@frontera-sdk/cli 1.52.38 → 1.52.40
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 +9 -0
- package/package.json +6 -4
- package/src/adopt.ts +6 -2
- package/src/api/apps-api.ts +86 -4
- package/src/api/blueprint-authoring-api.ts +35 -11
- package/src/api/dataset-api.ts +29 -0
- package/src/api/governed-action-api.ts +90 -2
- package/src/api/platform-api.ts +87 -2
- package/src/api/workflow-api.ts +33 -2
- package/src/app-contract.ts +156 -0
- package/src/app-lock.ts +125 -0
- package/src/args.ts +14 -3
- package/src/auth-verify.ts +10 -0
- package/src/automation-template.ts +12 -1
- package/src/blueprint/action-rules.ts +516 -0
- package/src/blueprint/action-semantic-fit.ts +205 -0
- package/src/blueprint/compile.ts +51 -2
- package/src/blueprint/diff.ts +34 -2
- package/src/blueprint/metric-query.ts +215 -0
- package/src/blueprint/metric-rules.ts +122 -0
- package/src/blueprint/object-sets.ts +116 -0
- package/src/blueprint/ontology-edit-plan.ts +48 -4
- package/src/blueprint/projection.ts +24 -1
- package/src/blueprint/scaffold.ts +84 -42
- package/src/commands/action/available.ts +1 -1
- package/src/commands/action/criteria-refusal.ts +84 -0
- package/src/commands/action/decide.ts +5 -2
- package/src/commands/action/deploy.ts +13 -4
- package/src/commands/action/list.ts +134 -19
- package/src/commands/action/requests.ts +34 -6
- package/src/commands/action/submit.ts +28 -13
- package/src/commands/agent/index-commands.ts +69 -7
- package/src/commands/agent/resolve.ts +2 -0
- package/src/commands/app/checks.ts +44 -0
- package/src/commands/app/deploy.ts +243 -60
- package/src/commands/app/deployments.ts +97 -0
- package/src/commands/app/dev.ts +103 -11
- package/src/commands/app/init.ts +36 -3
- package/src/commands/app/promote.ts +45 -19
- package/src/commands/app/pull.ts +3 -1
- package/src/commands/app/rollback.ts +46 -0
- package/src/commands/app/sdk.ts +54 -8
- package/src/commands/app/unpin.ts +27 -0
- package/src/commands/app/versions.ts +1 -1
- package/src/commands/automation/bindings.ts +4 -2
- package/src/commands/automation/build-entry.ts +4 -1
- package/src/commands/automation/index-commands.ts +3 -1
- package/src/commands/automation/pull.ts +6 -1
- package/src/commands/automation/run.ts +34 -9
- package/src/commands/blueprint/bind.ts +2 -1
- package/src/commands/blueprint/declarative.ts +331 -31
- package/src/commands/blueprint/get.ts +6 -2
- package/src/commands/blueprint/grants.ts +64 -10
- package/src/commands/blueprint/metric.ts +108 -0
- package/src/commands/blueprint/object-sets.ts +71 -0
- package/src/commands/blueprint/query.ts +155 -31
- package/src/commands/dataset/index-commands.ts +156 -7
- package/src/commands/knowledge/index-commands.ts +122 -20
- package/src/commands/local/check.ts +657 -44
- package/src/commands/local/data.ts +33 -12
- package/src/commands/local/dev.ts +875 -88
- package/src/commands/local/docs-mcp.ts +10 -18
- package/src/commands/local/index-commands.ts +300 -63
- package/src/commands/local/init.ts +18 -5
- package/src/commands/local/key.ts +0 -3
- package/src/commands/local/providers.ts +0 -7
- package/src/commands/local/remove.ts +116 -0
- package/src/commands/local/schema.ts +3 -4
- package/src/commands/local/sdk-refresh.ts +59 -0
- package/src/commands/local/snapshot.ts +100 -0
- package/src/commands/local/sync-request.ts +320 -13
- package/src/commands/local/sync.ts +102 -5
- package/src/commands/mcp/serve.ts +34 -0
- package/src/commands/registry.ts +52 -8
- package/src/commands/types.ts +29 -0
- package/src/commands/workflow/archive.ts +46 -0
- package/src/commands/workflow/get.ts +2 -1
- package/src/commands/workflow/index-commands.ts +5 -0
- package/src/commands/workflow/push.ts +49 -2
- package/src/commands/workflow/run.ts +65 -7
- package/src/commands/workflow/runs.ts +9 -6
- package/src/commands/workflow/webhook-test.ts +166 -0
- package/src/config.ts +43 -6
- package/src/dev-broker.ts +52 -0
- package/src/exit.ts +103 -4
- package/src/flag-help.ts +77 -17
- package/src/forge/touches.ts +15 -12
- package/src/function-outside-imports.ts +89 -0
- package/src/harness.ts +42 -24
- package/src/heal.ts +112 -1
- package/src/local/fix-words.ts +12 -0
- package/src/local/function-bundle-recovery.ts +775 -0
- package/src/local/local-actions.ts +103 -10
- package/src/local/local-agent-action-grants.ts +210 -0
- package/src/local/local-agent-checks.ts +232 -0
- package/src/local/local-agent-grants.ts +29 -7
- package/src/local/local-agent-lean-plugins.ts +68 -0
- package/src/local/local-agent-missing-tools.ts +68 -0
- package/src/local/local-agent-name.ts +27 -0
- package/src/local/local-app-code.ts +191 -0
- package/src/local/local-app-dev-files.ts +186 -0
- package/src/local/local-app-dev-session.ts +88 -0
- package/src/local/local-app-lock-install.ts +114 -0
- package/src/local/local-app-pulled.ts +167 -0
- package/src/local/local-app-restore.ts +111 -0
- package/src/local/local-app-runtime.ts +68 -0
- package/src/local/local-app-sdk-paths.ts +149 -0
- package/src/local/local-app-sdk.ts +509 -0
- package/src/local/local-applied.ts +211 -21
- package/src/local/local-apply-history.ts +84 -0
- package/src/local/local-apply-summary.ts +86 -0
- package/src/local/local-apply-watchdog.ts +95 -0
- package/src/local/local-apps.ts +180 -7
- package/src/local/local-blueprint-errors.ts +198 -0
- package/src/local/local-blueprint-outline.ts +31 -12
- package/src/local/local-blueprint-schemas.ts +582 -0
- package/src/local/local-build-inputs.ts +48 -0
- package/src/local/local-build-summary.ts +495 -28
- package/src/local/local-case-evidence.ts +120 -0
- package/src/local/local-case-not-run.ts +153 -0
- package/src/local/local-chat-apps.ts +93 -16
- package/src/local/local-check-inherited.ts +83 -0
- package/src/local/local-cities.ts +89 -0
- package/src/local/local-component-health.ts +119 -0
- package/src/local/local-control.ts +34 -7
- package/src/local/local-db-snapshot.ts +348 -0
- package/src/local/local-dev-references.ts +346 -35
- package/src/local/local-dev-sync.ts +136 -7
- package/src/local/local-docker.ts +35 -1
- package/src/local/local-docs-search.ts +1 -1
- package/src/local/local-docs.ts +281 -2
- package/src/local/local-empty-workflows.ts +92 -0
- package/src/local/local-env.ts +16 -1
- package/src/local/local-file-checks.ts +104 -0
- package/src/local/local-function-ids.ts +141 -0
- package/src/local/local-function-recovery.ts +141 -0
- package/src/local/local-function-state.ts +87 -0
- package/src/local/local-keys.ts +7 -5
- package/src/local/local-knowledge-ingestion.ts +225 -0
- package/src/local/local-knowledge-uploads.ts +217 -0
- package/src/local/local-media-placeholders.ts +182 -0
- package/src/local/local-media-sets.ts +90 -10
- package/src/local/local-model-parameters.ts +89 -0
- package/src/local/local-object-sets.ts +18 -6
- package/src/local/local-operating.ts +35 -1
- package/src/local/local-org-settings.ts +205 -7
- package/src/local/local-orphans.ts +321 -0
- package/src/local/local-personas.ts +296 -0
- package/src/local/local-portability.ts +141 -16
- package/src/local/local-ports.ts +99 -7
- package/src/local/local-project-notices.ts +234 -0
- package/src/local/local-project-secrets.ts +133 -0
- package/src/local/local-project.ts +12 -0
- package/src/local/local-provider-refusal.ts +66 -0
- package/src/local/local-request-evidence.ts +112 -0
- package/src/local/local-runtime-health.ts +123 -0
- package/src/local/local-runtime-history.ts +50 -0
- package/src/local/local-runtime-target.ts +42 -0
- package/src/local/local-sample-data.ts +141 -0
- package/src/local/local-sample-remake.ts +138 -0
- package/src/local/local-scaffold.ts +336 -53
- package/src/local/local-schema-check.ts +148 -0
- package/src/local/local-schemas.ts +742 -25
- package/src/local/local-secret-carry.ts +107 -0
- package/src/local/local-secrets.ts +38 -8
- package/src/local/local-seed.ts +102 -26
- package/src/local/local-stand-ins.ts +1 -1
- package/src/local/local-state.ts +43 -2
- package/src/local/local-studio-server.ts +170 -7
- package/src/local/local-supervisor.ts +44 -6
- package/src/local/local-sync-names.ts +2 -0
- package/src/local/local-sync-plan.ts +11 -0
- package/src/local/local-sync-push.ts +10 -7
- package/src/local/local-sync-refs.ts +49 -3
- package/src/local/local-sync-remote.ts +343 -32
- package/src/local/local-sync-snapshot.ts +15 -10
- package/src/local/local-synthetic-data.ts +2116 -99
- package/src/local/local-test-baseline.ts +113 -0
- package/src/local/local-test-cases.ts +76 -1
- package/src/local/local-test-runner.ts +582 -91
- package/src/local/local-test-runs.ts +13 -5
- package/src/local/local-vendored-sdk.ts +183 -0
- package/src/local/local-workflow-state.ts +16 -3
- package/src/local/local-workflow-tests.ts +482 -0
- package/src/local/local-workspace-grants.ts +81 -0
- package/src/main.ts +50 -9
- package/src/mcp/a11y-scan.ts +55 -0
- package/src/mcp/browser/ax-snapshot.ts +181 -0
- package/src/mcp/browser/credentials.ts +78 -0
- package/src/mcp/browser/frames.ts +336 -0
- package/src/mcp/browser/headless-session.ts +413 -0
- package/src/mcp/browser/headless-tab.ts +575 -0
- package/src/mcp/browser/keys.ts +70 -0
- package/src/mcp/browser/page-scripts.ts +129 -0
- package/src/mcp/browser/wait.ts +73 -0
- package/src/mcp/forge-link.ts +126 -0
- package/src/mcp/headless.ts +199 -0
- package/src/mcp/mcp-apps.ts +435 -0
- package/src/mcp/mcp-chat.ts +226 -0
- package/src/mcp/mcp-cli.ts +53 -0
- package/src/mcp/mcp-protocol.ts +122 -0
- package/src/mcp/mcp-runtime.ts +113 -0
- package/src/mcp/mcp-server.ts +108 -0
- package/src/mcp/mcp-traces.ts +409 -0
- package/src/mcp/page-observation.ts +193 -0
- package/src/mcp/tools/agent-tools.ts +700 -0
- package/src/mcp/tools/app-tools.ts +241 -0
- package/src/mcp/tools/browser-tools.ts +310 -0
- package/src/mcp/tools/docs-tools.ts +32 -0
- package/src/mcp/tools/project-tools.ts +205 -0
- package/src/mcp/tools/test-tools.ts +339 -0
- package/src/mcp/tools/workflow-tools.ts +611 -0
- package/src/output.ts +1 -1
- package/src/pack.ts +8 -3
- package/src/packaging.ts +1 -1
- package/src/project-bootstrap.ts +107 -4
- package/src/project-context.ts +18 -1
- package/src/project.ts +27 -2
- package/src/provenance.ts +25 -0
- package/src/render-evidence.ts +65 -7
- package/src/template.ts +14 -8
- package/src/templates/next-app-files.ts +35 -6
- package/src/templates/next-skills.ts +61 -27
- package/src/vendor/kit-assets.json +6 -6
- package/src/vendor/sdk-sources.json +24 -21
|
@@ -5,6 +5,7 @@ import { join } from 'node:path'
|
|
|
5
5
|
import {
|
|
6
6
|
containerState,
|
|
7
7
|
copyFromContainer,
|
|
8
|
+
dbVolumeName,
|
|
8
9
|
execInContainer,
|
|
9
10
|
followLogs,
|
|
10
11
|
imagePresent,
|
|
@@ -13,6 +14,7 @@ import {
|
|
|
13
14
|
runContainer,
|
|
14
15
|
startContainer,
|
|
15
16
|
stopContainer,
|
|
17
|
+
storageVolumeName,
|
|
16
18
|
} from './local-docker'
|
|
17
19
|
import {
|
|
18
20
|
CATALOG_DB,
|
|
@@ -30,14 +32,15 @@ import {
|
|
|
30
32
|
type LocalConfig,
|
|
31
33
|
type LocalSecrets,
|
|
32
34
|
} from './local-env'
|
|
33
|
-
import {
|
|
35
|
+
import { localEmbeddingsReachable, providerEnv } from './local-keys'
|
|
34
36
|
import { applyProviderSetup, readProviderSetup, rememberSlugs, writeProviderSetup } from './local-providers'
|
|
35
37
|
import type { PortBlock } from './local-ports'
|
|
36
38
|
import { startProcess, stopProcess, waitForHttp } from './local-process'
|
|
37
39
|
import type { LocalProject } from './local-project'
|
|
38
40
|
import { startLocalEmbeddings } from './local-embeddings'
|
|
39
41
|
import { LOCAL_TRACE_KEYS, startLocalTraces } from './local-traces'
|
|
40
|
-
import {
|
|
42
|
+
import { recordRuntime } from './local-runtime-history'
|
|
43
|
+
import { buildDocs, checkoutDocsStale, copyDocs, customerDocsBundle, IMAGE_DOCS_PATH, writeDocsBuildMarker } from './local-docs'
|
|
41
44
|
import { keepSchemas, type SchemaSource } from './local-schemas'
|
|
42
45
|
import { applyModelKeys, ensureBlueprint, ensureOrgKey, ensureProviderModels, retireLocalModel, seedLocalRuntime, signIn } from './local-seed'
|
|
43
46
|
import {
|
|
@@ -80,7 +83,7 @@ export const IMAGES = {
|
|
|
80
83
|
export const containerName = (project: LocalProject, component: ComponentName) =>
|
|
81
84
|
`frontera-${project.id}-${component}`
|
|
82
85
|
|
|
83
|
-
export const volumeName = (project: LocalProject) =>
|
|
86
|
+
export const volumeName = (project: LocalProject) => dbVolumeName(project.id)
|
|
84
87
|
|
|
85
88
|
interface Running {
|
|
86
89
|
children: Map<ComponentName, ChildProcess>
|
|
@@ -144,6 +147,8 @@ export async function runSupervisor(project: LocalProject, ports: PortBlock, ses
|
|
|
144
147
|
}
|
|
145
148
|
for (const name of [...running.containers].reverse()) stopContainer(name)
|
|
146
149
|
for (const c of state.components) if (c.status === 'running' || c.status === 'starting') set(c.name, { status: 'stopped' })
|
|
150
|
+
// A failed start says so where it failed (`fail`); this is the stop of a runtime that was up.
|
|
151
|
+
if (code === 0) recordRuntime(project, { event: 'stopped', at: new Date().toISOString() })
|
|
147
152
|
state = { ...state, phase: 'stopped', supervisorPid: undefined }
|
|
148
153
|
save()
|
|
149
154
|
process.exit(code)
|
|
@@ -155,6 +160,7 @@ export async function runSupervisor(project: LocalProject, ports: PortBlock, ses
|
|
|
155
160
|
set(name, { status: 'failed', detail })
|
|
156
161
|
state = { ...state, phase: 'failed', error: `${name}: ${detail}` }
|
|
157
162
|
save()
|
|
163
|
+
recordRuntime(project, { event: 'failed', at: new Date().toISOString(), since: state.startedAt, detail: `${name}: ${detail}` })
|
|
158
164
|
await shutdown(1)
|
|
159
165
|
}
|
|
160
166
|
|
|
@@ -278,6 +284,7 @@ export async function runSupervisor(project: LocalProject, ports: PortBlock, ses
|
|
|
278
284
|
set('studio', { status: 'running', startedAt: new Date().toISOString(), pid: process.pid })
|
|
279
285
|
state = { ...state, phase: 'ready', error: undefined }
|
|
280
286
|
save()
|
|
287
|
+
recordRuntime(project, { event: 'started', at: new Date().toISOString(), since: state.startedAt })
|
|
281
288
|
} catch (err) {
|
|
282
289
|
const failing = state.components.find((c) => c.status === 'starting')?.name ?? 'service'
|
|
283
290
|
await fail(failing, (err as Error).message)
|
|
@@ -393,7 +400,7 @@ async function startStorage(project: LocalProject, ports: PortBlock, secrets: Lo
|
|
|
393
400
|
projectId: project.id,
|
|
394
401
|
ports: [{ host: ports.storage, container: 9000 }],
|
|
395
402
|
env: { RUSTFS_ACCESS_KEY: secrets.storageAccessKey, RUSTFS_SECRET_KEY: secrets.storageSecretKey },
|
|
396
|
-
volumes: [{ name:
|
|
403
|
+
volumes: [{ name: storageVolumeName(project.id), path: '/data' }],
|
|
397
404
|
memory: '256m',
|
|
398
405
|
cpus: '0.5',
|
|
399
406
|
args: ['server', '/data'],
|
|
@@ -462,7 +469,7 @@ async function startService(
|
|
|
462
469
|
runnerEnabled,
|
|
463
470
|
jobsEnabled,
|
|
464
471
|
storageEnabled,
|
|
465
|
-
keylessEmbeddings:
|
|
472
|
+
keylessEmbeddings: localEmbeddingsReachable(),
|
|
466
473
|
documentationBundle: config.mode === 'source' ? customerDocsBundle(config.platformSource) : null,
|
|
467
474
|
})
|
|
468
475
|
const log = logPath(project, 'service')
|
|
@@ -479,6 +486,7 @@ async function startService(
|
|
|
479
486
|
if (!ok) throw new Error('the service did not become healthy; see `frontera local logs service`')
|
|
480
487
|
set('service', { status: 'running', pid: child.pid, startedAt: new Date().toISOString() })
|
|
481
488
|
keepDocs(project, { bundle: customerDocsBundle(config.platformSource) }, log)
|
|
489
|
+
refreshCheckoutDocs(project, config.platformSource, log)
|
|
482
490
|
return
|
|
483
491
|
}
|
|
484
492
|
const name = containerName(project, 'service')
|
|
@@ -504,7 +512,7 @@ async function startService(
|
|
|
504
512
|
keepDocs(project, { fromContainer: (dest) => copyFromContainer(name, IMAGE_DOCS_PATH, dest) }, log)
|
|
505
513
|
}
|
|
506
514
|
|
|
507
|
-
/** The release's documentation, for the solution's coding agents (`
|
|
515
|
+
/** The release's documentation, for the solution's coding agents (`frontera mcp`). */
|
|
508
516
|
function keepDocs(project: LocalProject, from: { bundle?: string | null; fromContainer?: (dest: string) => boolean }, log: string): void {
|
|
509
517
|
if (!from.bundle && !from.fromContainer) {
|
|
510
518
|
appendFileSync(log, '[docs] no customer documentation in the platform checkout; run `bun run docs:build` there\n')
|
|
@@ -514,6 +522,36 @@ function keepDocs(project: LocalProject, from: { bundle?: string | null; fromCon
|
|
|
514
522
|
if (problem) appendFileSync(log, `[docs] the documentation for coding agents was not copied: ${problem}\n`)
|
|
515
523
|
}
|
|
516
524
|
|
|
525
|
+
/**
|
|
526
|
+
* From a checkout whose documentation build is behind it (no build, guides
|
|
527
|
+
* changed since, another version): build it in the background, at low
|
|
528
|
+
* priority, and refresh the copy when it is done. Start never waits for it:
|
|
529
|
+
* the copy from the previous build serves until then, and the service rereads
|
|
530
|
+
* the checkout's publication when it changes on disk.
|
|
531
|
+
*/
|
|
532
|
+
function refreshCheckoutDocs(project: LocalProject, platformSource: string | undefined, log: string): void {
|
|
533
|
+
if (!platformSource) return
|
|
534
|
+
const why = checkoutDocsStale(platformSource)
|
|
535
|
+
if (!why) {
|
|
536
|
+
// Current: a failure an earlier start recorded no longer applies.
|
|
537
|
+
writeDocsBuildMarker(project, null)
|
|
538
|
+
return
|
|
539
|
+
}
|
|
540
|
+
appendFileSync(log, `[docs] ${why}; building it in the background (nice -n 10 bun run docs:build); the previous copy serves until it is done\n`)
|
|
541
|
+
// What `local status` and Forge's Docs say while it builds ("rebuilding"), and if it fails.
|
|
542
|
+
writeDocsBuildMarker(project, { state: 'building', at: new Date().toISOString(), pid: process.pid, why })
|
|
543
|
+
void buildDocs(platformSource, log).then((built) => {
|
|
544
|
+
if (!built) {
|
|
545
|
+
writeDocsBuildMarker(project, { state: 'failed', at: new Date().toISOString(), why })
|
|
546
|
+
appendFileSync(log, '[docs] the documentation build did not finish; coding agents keep the previous copy (`bun run docs:build` in the checkout shows why)\n')
|
|
547
|
+
return
|
|
548
|
+
}
|
|
549
|
+
keepDocs(project, { bundle: customerDocsBundle(platformSource) }, log)
|
|
550
|
+
writeDocsBuildMarker(project, null)
|
|
551
|
+
appendFileSync(log, '[docs] built the documentation and refreshed the copy for coding agents\n')
|
|
552
|
+
})
|
|
553
|
+
}
|
|
554
|
+
|
|
517
555
|
/** The formats of the project's files, for its coding agents and editors (`local schema`). Logged, never a reason to fail start. */
|
|
518
556
|
async function keepFileSchemas(project: LocalProject, source: SchemaSource, log: string): Promise<void> {
|
|
519
557
|
for (const problem of await keepSchemas(project, source)) appendFileSync(log, `[schemas] not kept: ${problem}\n`)
|
|
@@ -131,6 +131,8 @@ export function itemTitle(kind: string, name: string, files: Record<string, stri
|
|
|
131
131
|
const own = declared(doc?.config, 'name') ?? declared(doc, 'name')
|
|
132
132
|
if (own) return own
|
|
133
133
|
}
|
|
134
|
+
// The workspace's own grants beyond read (`blueprint/workspace-grants.yaml`): one file, no display name.
|
|
135
|
+
if (kind === 'blueprint' && name === 'workspace-grants') return 'Workspace grants'
|
|
134
136
|
if (kind === 'blueprint') {
|
|
135
137
|
const [dir, apiName] = name.split('/')
|
|
136
138
|
const file = Object.entries(files).find(([p]) => p.startsWith(`blueprint/${dir}/${apiName}.`))?.[1]
|
|
@@ -84,6 +84,13 @@ export interface RemoteItem {
|
|
|
84
84
|
* is being read; `from`, the snapshot's copy, once fetched.
|
|
85
85
|
*/
|
|
86
86
|
assets?: ItemAsset[]
|
|
87
|
+
/**
|
|
88
|
+
* Files the pull recovered from the item's deployed bundle because its
|
|
89
|
+
* source lacks them (a Function's imports from beside its folder), by
|
|
90
|
+
* project path. Pulled like any other file; listed so it is never mistaken
|
|
91
|
+
* for the original source.
|
|
92
|
+
*/
|
|
93
|
+
recovered?: string[]
|
|
87
94
|
}
|
|
88
95
|
|
|
89
96
|
export interface ItemAsset {
|
|
@@ -193,6 +200,8 @@ export interface LockEntry {
|
|
|
193
200
|
pulledAt: string
|
|
194
201
|
/** Pulled with conflicts not yet resolved; the base above is still the last clean pull. */
|
|
195
202
|
conflict?: LockConflict
|
|
203
|
+
/** Its files the pull recovered from the deployed bundle, not its source (`RemoteItem.recovered`). */
|
|
204
|
+
recovered?: string[]
|
|
196
205
|
}
|
|
197
206
|
|
|
198
207
|
/**
|
|
@@ -245,6 +254,7 @@ function sortEntry(entry: LockEntry): LockEntry {
|
|
|
245
254
|
...(entry.state ? { state: entry.state } : {}),
|
|
246
255
|
pulledAt: entry.pulledAt,
|
|
247
256
|
...(entry.conflict ? { conflict: sortConflict(entry.conflict) } : {}),
|
|
257
|
+
...(entry.recovered?.length ? { recovered: [...entry.recovered].sort() } : {}),
|
|
248
258
|
}
|
|
249
259
|
}
|
|
250
260
|
|
|
@@ -475,6 +485,7 @@ function entryFor(item: RemoteItem, now: string): LockEntry {
|
|
|
475
485
|
...(item.version ? { version: item.version } : {}),
|
|
476
486
|
...(item.state ? { state: item.state } : {}),
|
|
477
487
|
pulledAt: now,
|
|
488
|
+
...(item.recovered?.length ? { recovered: [...item.recovered].sort() } : {}),
|
|
478
489
|
}
|
|
479
490
|
}
|
|
480
491
|
|
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
import { createHash } from 'node:crypto'
|
|
2
|
-
import {
|
|
2
|
+
import { existsSync, lstatSync, mkdirSync, readdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
|
|
3
3
|
import { dirname, join, posix } from 'node:path'
|
|
4
4
|
|
|
5
|
+
import { ignoreInFronteraDir } from '../project-context'
|
|
5
6
|
import matter from 'gray-matter'
|
|
6
7
|
import { parse as parseYaml } from 'yaml'
|
|
7
8
|
|
|
8
9
|
import { mimeFromPath } from '../commands/skill/bundle-commands'
|
|
9
10
|
import { writeFileAtomic } from '../fs-atomic'
|
|
10
11
|
import { CliError } from '../errors'
|
|
12
|
+
import { readWorkspaceGrants, WORKSPACE_GRANTS_FILE, WORKSPACE_GRANTS_NAME, WORKSPACE_GRANTS_REF } from './local-workspace-grants'
|
|
11
13
|
import { documentChanges, localDocuments, splitDocumentPath, type PulledDocument } from './local-knowledge-documents'
|
|
12
14
|
import { QUALITY_CHECKS_FILE, qualityChecksPath, readQualityChecks } from './local-quality-checks'
|
|
13
15
|
import { lessonsPath, lessonsProposal } from './local-lessons'
|
|
@@ -266,6 +268,8 @@ export function projectRefs(root: string): Map<string, { kind: SyncKind; name: s
|
|
|
266
268
|
for (const n of dirs('skills')) if (existsSync(join(root, 'skills', n, 'SKILL.md'))) add('skill', n)
|
|
267
269
|
for (const n of dirs('knowledge')) if (existsSync(join(root, 'knowledge', n, 'knowledge.json'))) add('knowledge', n)
|
|
268
270
|
for (const dir of BLUEPRINT_DIRS) for (const f of dirs(`blueprint/${dir}`)) if (/\.(ya?ml|json)$/.test(f)) add('blueprint', `${dir}/${f.replace(/\.(ya?ml|json)$/, '')}`)
|
|
271
|
+
// The workspace's own grants beyond read: one Blueprint item, `blueprint/workspace-grants`.
|
|
272
|
+
if (existsSync(join(root, WORKSPACE_GRANTS_FILE))) add('blueprint', WORKSPACE_GRANTS_NAME)
|
|
269
273
|
for (const f of dirs('mcp')) if (f.endsWith('.plugin.json') && f !== '.plugin.json') add('plugin', f.slice(0, -'.plugin.json'.length))
|
|
270
274
|
for (const f of dirs('chat-apps')) if (f.endsWith('.json') && f !== '.json') add('chat-app', f.slice(0, -'.json'.length))
|
|
271
275
|
for (const slug of dirs('agents')) if (existsSync(join(root, lessonsPath(slug)))) add('lessons', slug)
|
|
@@ -441,12 +445,15 @@ export function pushCandidates(input: CandidateInput): PushItem[] {
|
|
|
441
445
|
else if (status === 'conflict') block('moved', BLOCK_REASON.moved)
|
|
442
446
|
else if (status === 'unresolved') block('conflicts', BLOCK_REASON.conflicts)
|
|
443
447
|
else if (deleted && NOT_DELETED[kind]) block('shape', NOT_DELETED[kind]!)
|
|
448
|
+
// Never proposed as a deletion: the platform refuses one, since deleting the file changes no grant.
|
|
449
|
+
else if (deleted && ref === WORKSPACE_GRANTS_REF) block('shape', `Deleting ${WORKSPACE_GRANTS_FILE} changes no grant: to take read_sensitive away, put the file back and list the type with \`permissions: [read]\``)
|
|
444
450
|
else if (status === 'deleted-remote') block('workspace-deleted', BLOCK_REASON['workspace-deleted'])
|
|
445
451
|
else if (deleted) {
|
|
446
452
|
// Proposable: Apply deletes it there, after everything that names it is updated.
|
|
447
453
|
} else if (problem) block('shape', problem)
|
|
448
454
|
else if (!Object.keys(files).length) block('shape', 'No files to propose')
|
|
449
455
|
else if (kind === 'blueprint' && Object.keys(files).length > 1) block('shape', `${Object.keys(files).join(' and ')}: keep one file per Blueprint artifact`)
|
|
456
|
+
else if (ref === WORKSPACE_GRANTS_REF && readWorkspaceGrants(root)?.problems.length) block('shape', readWorkspaceGrants(root)!.problems.join('; '))
|
|
450
457
|
else if (assetState?.changed && entry?.revision && theirs?.revision && entry.revision !== theirs.revision && textSame) {
|
|
451
458
|
// A pull never writes over an asset the folder has, so one the workspace changed since reads as a change here.
|
|
452
459
|
block('moved', 'Its assets changed in the workspace since your last pull: delete the copies here to bring the workspace’s, then propose')
|
|
@@ -577,12 +584,8 @@ const REQUESTS_DIR = join(SYNC_DIR, 'requests')
|
|
|
577
584
|
|
|
578
585
|
/** `.frontera/sync/` stays out of git, the requests with it. */
|
|
579
586
|
function ignoreSync(root: string): void {
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
const text = existsSync(ignore) ? readFileSync(ignore, 'utf8') : ''
|
|
583
|
-
if (text.split(/\r?\n/).some((l) => l.trim() === 'sync/' || l.trim() === 'sync')) return
|
|
584
|
-
if (text) appendFileSync(ignore, `${text.endsWith('\n') ? '' : '\n'}sync/\n`)
|
|
585
|
-
else writeFileSync(ignore, 'local/\nsync/\n')
|
|
587
|
+
// A pulled or linked project: its sync state, and this machine's profile binding, stay out of git.
|
|
588
|
+
ignoreInFronteraDir(root, ['local/', 'sync/'])
|
|
586
589
|
}
|
|
587
590
|
|
|
588
591
|
/** The requests file for this workspace; another workspace's (a relinked project) reads as none. */
|
|
@@ -15,6 +15,8 @@
|
|
|
15
15
|
* directory knows, is rewritten.
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
|
+
import { slugify } from './local-project'
|
|
19
|
+
|
|
18
20
|
/**
|
|
19
21
|
* `object` is an object definition an Agent's object tools work on
|
|
20
22
|
* (`object/claim_ticket`), by slug; its fields travel in the Agent's file.
|
|
@@ -24,7 +26,50 @@ export type RefKind = (typeof REF_KINDS)[number]
|
|
|
24
26
|
|
|
25
27
|
/** Names as resources are named: slugs, API names, install names. */
|
|
26
28
|
const NAME = '[A-Za-z0-9][A-Za-z0-9._-]*'
|
|
27
|
-
|
|
29
|
+
/**
|
|
30
|
+
* Kinds people name in words: a saved object set ("Open work orders") and a
|
|
31
|
+
* knowledge base. A reference to one may hold the name as people say it,
|
|
32
|
+
* spaces and all (`objectSet/Open work orders`), or its name as a reference
|
|
33
|
+
* holds it (`objectSet/open-work-orders`, `refName`); both resolve.
|
|
34
|
+
*/
|
|
35
|
+
export const WORDED_KINDS: readonly RefKind[] = ['objectSet', 'knowledge']
|
|
36
|
+
/** A worded name: no leading or trailing space, no slash, no line break. */
|
|
37
|
+
const WORDS = '[A-Za-z0-9](?:[A-Za-z0-9 ._()&\'’,-]*[A-Za-z0-9._)])?'
|
|
38
|
+
const PLAIN_KINDS = REF_KINDS.filter((k) => !WORDED_KINDS.includes(k))
|
|
39
|
+
const REF = new RegExp(`^(?:(${PLAIN_KINDS.join('|')})/(${NAME})|(${WORDED_KINDS.join('|')})/(${WORDS}))$`)
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* The pattern a whole-value reference of these kinds matches (`^(function|workflow)/<name>$`),
|
|
43
|
+
* the same one `parseRef` reads: what `local schema` tells an editor a project file may write
|
|
44
|
+
* where the runtime wants an id.
|
|
45
|
+
*/
|
|
46
|
+
export function refPattern(kinds: readonly RefKind[] = REF_KINDS): string {
|
|
47
|
+
const plain = kinds.filter((k) => !WORDED_KINDS.includes(k))
|
|
48
|
+
const worded = kinds.filter((k) => WORDED_KINDS.includes(k))
|
|
49
|
+
const parts = [...(plain.length ? [`(${plain.join('|')})/${NAME}`] : []), ...(worded.length ? [`(${worded.join('|')})/${WORDS}`] : [])]
|
|
50
|
+
return parts.length === 1 ? `^${parts[0]}$` : `^(?:${parts.join('|')})$`
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* A resource's name as a reference holds it: as it is when it is already a
|
|
55
|
+
* slug or API name, else its words made a slug (`Open work orders` →
|
|
56
|
+
* `open-work-orders`, as `slugify` makes one, at most 24 characters).
|
|
57
|
+
*/
|
|
58
|
+
export function refName(name: string): string {
|
|
59
|
+
return /^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(name) ? name : slugify(name) || 'unnamed'
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* A name's id in one kind's map: as written, or for a kind named in words,
|
|
64
|
+
* by its name as a reference holds it (`objectSet/Open work orders` finds the
|
|
65
|
+
* set the directory lists as `open-work-orders`).
|
|
66
|
+
*/
|
|
67
|
+
export function lookupRef(names: ReadonlyMap<string, string> | undefined, kind: RefKind, name: string): string | undefined {
|
|
68
|
+
if (!names) return undefined
|
|
69
|
+
const exact = names.get(name)
|
|
70
|
+
if (exact !== undefined || !WORDED_KINDS.includes(kind)) return exact
|
|
71
|
+
return names.get(refName(name))
|
|
72
|
+
}
|
|
28
73
|
/** `[[function:<id or name>]]` in guidance text, the platform's link form. */
|
|
29
74
|
const LINK = new RegExp(`\\[\\[(${REF_KINDS.join('|')}):([^\\]\\s]+)\\]\\]`, 'g')
|
|
30
75
|
/** `function:<id or name>` as a whole value: a procedure's tool list names its tools this way. */
|
|
@@ -37,7 +82,8 @@ export function isUuid(value: string): boolean {
|
|
|
37
82
|
|
|
38
83
|
export function parseRef(value: string): { kind: RefKind; name: string } | null {
|
|
39
84
|
const m = REF.exec(value)
|
|
40
|
-
|
|
85
|
+
if (!m) return null
|
|
86
|
+
return m[1] ? { kind: m[1] as RefKind, name: m[2]! } : { kind: m[3] as RefKind, name: m[4]! }
|
|
41
87
|
}
|
|
42
88
|
|
|
43
89
|
export function formatRef(kind: RefKind, name: string): string {
|
|
@@ -147,7 +193,7 @@ export function toIds<T>(doc: T, directory: Directory): { doc: T; unresolved: Un
|
|
|
147
193
|
const out = walk(doc, [], (s, path) => {
|
|
148
194
|
const ref = parseRef(s)
|
|
149
195
|
if (ref) {
|
|
150
|
-
const id = directory[ref.kind]
|
|
196
|
+
const id = lookupRef(directory[ref.kind], ref.kind, ref.name)
|
|
151
197
|
if (id) return id
|
|
152
198
|
unresolved.push({ path: path.join('.'), value: s, kind: ref.kind })
|
|
153
199
|
return s
|