@kal-elsam/kairo-runtime 0.4.2 → 0.5.0
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 +124 -17
- package/global-template/components/catalog.json +63 -6
- package/global-template/components/sdd-core/personas/teaching.md +42 -0
- package/global-template/components/sdd-core/skills/sdd-apply/SKILL.md +34 -0
- package/global-template/components/sdd-core/skills/sdd-apply/references/contract.md +4 -0
- package/global-template/components/sdd-core/skills/sdd-archive/SKILL.md +34 -0
- package/global-template/components/sdd-core/skills/sdd-archive/references/contract.md +4 -0
- package/global-template/components/sdd-core/skills/sdd-design/SKILL.md +34 -0
- package/global-template/components/sdd-core/skills/sdd-design/references/contract.md +4 -0
- package/global-template/components/sdd-core/skills/sdd-explore/SKILL.md +34 -0
- package/global-template/components/sdd-core/skills/sdd-explore/references/contract.md +4 -0
- package/global-template/components/sdd-core/skills/sdd-init/SKILL.md +34 -0
- package/global-template/components/sdd-core/skills/sdd-init/references/contract.md +4 -0
- package/global-template/components/sdd-core/skills/sdd-propose/SKILL.md +35 -0
- package/global-template/components/sdd-core/skills/sdd-propose/references/contract.md +4 -0
- package/global-template/components/sdd-core/skills/sdd-spec/SKILL.md +34 -0
- package/global-template/components/sdd-core/skills/sdd-spec/references/contract.md +4 -0
- package/global-template/components/sdd-core/skills/sdd-tasks/SKILL.md +34 -0
- package/global-template/components/sdd-core/skills/sdd-tasks/references/contract.md +4 -0
- package/global-template/components/sdd-core/skills/sdd-verify/SKILL.md +34 -0
- package/global-template/components/sdd-core/skills/sdd-verify/references/contract.md +4 -0
- package/package.json +1 -1
- package/scripts/cockpit-smoke.mjs +29 -8
- package/scripts/smoke-test.sh +9 -0
- package/src/cli.js +54 -5
- package/src/global/action-planner.js +17 -4
- package/src/global/component-authoring.js +1 -2
- package/src/global/component-ecosystem-checks.js +74 -15
- package/src/global/component-health.js +56 -0
- package/src/global/component-integration-cli.js +302 -0
- package/src/global/component-manifest.js +214 -0
- package/src/global/component-registry.js +19 -2
- package/src/global/component-resolve.js +51 -0
- package/src/global/components/sdd-core.js +6 -0
- package/src/global/control-plane-proposals.js +229 -0
- package/src/global/control-plane-snapshot.js +212 -0
- package/src/global/dashboard-guidance.js +27 -3
- package/src/global/diff.js +2 -5
- package/src/global/global-cli.js +16 -1
- package/src/global/global-doctor.js +75 -2
- package/src/global/global-installer.js +43 -6
- package/src/global/governance-actions.js +212 -0
- package/src/global/governance-repair.js +13 -0
- package/src/global/ink/cockpit-changes.js +124 -0
- package/src/global/ink/cockpit-control-center.js +106 -0
- package/src/global/ink/cockpit-controller.js +17 -0
- package/src/global/ink/cockpit-enter.js +27 -0
- package/src/global/ink/cockpit-focus.js +7 -1
- package/src/global/ink/cockpit-models.js +98 -46
- package/src/global/ink/cockpit-proposals.js +82 -0
- package/src/global/ink/cockpit-recovery.js +120 -0
- package/src/global/ink/cockpit-runs.js +47 -0
- package/src/global/ink/cockpit-scan.js +60 -0
- package/src/global/ink/cockpit-views.js +144 -74
- package/src/global/ink/orchestrator-app.js +156 -42
- package/src/global/ink/orchestrator-state.js +22 -1
- package/src/global/ink/use-orchestrator-data.js +239 -17
- package/src/global/integrations/engram-apply.js +105 -0
- package/src/global/integrations/engram-evidence.js +218 -0
- package/src/global/integrations/engram-exec.js +107 -0
- package/src/global/integrations/engram-plan.js +60 -0
- package/src/global/integrations/engram-provider.js +22 -0
- package/src/global/integrations/engram-receipts.js +116 -0
- package/src/global/integrations/engram-rollback.js +131 -0
- package/src/global/integrations/index.js +12 -0
- package/src/global/integrations/provider-contract.js +83 -0
- package/src/global/integrations/provider-registry.js +47 -0
- package/src/global/integrations/sdd-apply.js +190 -0
- package/src/global/integrations/sdd-destinations.js +95 -0
- package/src/global/integrations/sdd-evidence.js +89 -0
- package/src/global/integrations/sdd-fs-guard.js +183 -0
- package/src/global/integrations/sdd-lifecycle.js +84 -0
- package/src/global/integrations/sdd-persona.js +71 -0
- package/src/global/integrations/sdd-plan.js +87 -0
- package/src/global/integrations/sdd-provider.js +21 -0
- package/src/global/integrations/sdd-receipts.js +44 -0
- package/src/global/integrations/sdd-rollback.js +269 -0
- package/src/global/integrations/sdd-skill-files.js +61 -0
- package/src/global/integrations/sdd-state.js +175 -0
- package/src/global/integrations/sdd-verify.js +122 -0
- package/src/global/intelligence/backends/opencode-catalog.js +212 -0
- package/src/global/intelligence/backends/opencode-cli.js +247 -0
- package/src/global/intelligence/backends/opencode-evidence.js +129 -0
- package/src/global/intelligence/backends/opencode-http.js +152 -0
- package/src/global/intelligence/backends/opencode-invoke.js +83 -0
- package/src/global/intelligence/backends/opencode-providers.js +67 -0
- package/src/global/intelligence/backends/opencode-runtime.js +242 -0
- package/src/global/intelligence/context-compiler.js +95 -50
- package/src/global/intelligence/index.js +38 -1
- package/src/global/intelligence/orchestrate.js +4 -2
- package/src/global/intelligence/registry.js +62 -6
- package/src/global/intelligence/router.js +188 -70
- package/src/global/intelligence/transport-registry.js +147 -0
- package/src/global/intelligence/types.js +21 -2
- package/src/global/intelligence-cli.js +48 -17
- package/src/global/intelligence-session.js +37 -0
- package/src/global/json-output.js +1 -0
- package/src/global/load-component-catalog.js +10 -28
- package/src/global/load-workspace-component-catalog.js +22 -63
- package/src/global/state-migration.js +21 -1
- package/src/global/state.js +13 -8
- package/src/global/status.js +4 -17
- package/src/global/sync.js +2 -5
|
@@ -9,12 +9,14 @@ import {
|
|
|
9
9
|
} from "./intelligence/index.js";
|
|
10
10
|
import { BRAND } from "./brand/index.js";
|
|
11
11
|
import { formatCliCommand } from "./brand/cli.js";
|
|
12
|
+
import { resolveSessionOverride } from "./intelligence-session.js";
|
|
12
13
|
|
|
13
14
|
export async function runIntelligenceCli(options, packageManifest) {
|
|
14
15
|
const action = options.intelligenceAction ?? "status";
|
|
15
16
|
const homeDir = resolveHomeDir();
|
|
16
17
|
const workspaceRoot = options.cwd;
|
|
17
18
|
const { profile, sources } = await resolveProfile({ homeDir, workspaceRoot });
|
|
19
|
+
const sessionOverride = resolveSessionOverride(options);
|
|
18
20
|
|
|
19
21
|
switch (action) {
|
|
20
22
|
case "status":
|
|
@@ -24,13 +26,15 @@ export async function runIntelligenceCli(options, packageManifest) {
|
|
|
24
26
|
profile,
|
|
25
27
|
sources,
|
|
26
28
|
json: options.json,
|
|
27
|
-
env: process.env
|
|
29
|
+
env: process.env,
|
|
30
|
+
sessionOverride
|
|
28
31
|
});
|
|
29
32
|
case "models":
|
|
30
33
|
return printIntelligenceModels({
|
|
31
34
|
profile,
|
|
32
35
|
json: options.json,
|
|
33
|
-
env: process.env
|
|
36
|
+
env: process.env,
|
|
37
|
+
sessionOverride
|
|
34
38
|
});
|
|
35
39
|
case "context":
|
|
36
40
|
return printIntelligenceContext({
|
|
@@ -49,7 +53,8 @@ export async function runIntelligenceCli(options, packageManifest) {
|
|
|
49
53
|
task: options.intelligenceTask,
|
|
50
54
|
cloudConsent: options.cloudConsent,
|
|
51
55
|
json: options.json,
|
|
52
|
-
env: process.env
|
|
56
|
+
env: process.env,
|
|
57
|
+
sessionOverride
|
|
53
58
|
});
|
|
54
59
|
case "ask":
|
|
55
60
|
return runIntelligenceAsk({
|
|
@@ -63,7 +68,8 @@ export async function runIntelligenceCli(options, packageManifest) {
|
|
|
63
68
|
confirmed: options.yes || options.confirm,
|
|
64
69
|
json: options.json,
|
|
65
70
|
env: process.env,
|
|
66
|
-
packageManifest
|
|
71
|
+
packageManifest,
|
|
72
|
+
sessionOverride
|
|
67
73
|
});
|
|
68
74
|
default:
|
|
69
75
|
throw new Error(
|
|
@@ -72,7 +78,15 @@ export async function runIntelligenceCli(options, packageManifest) {
|
|
|
72
78
|
}
|
|
73
79
|
}
|
|
74
80
|
|
|
75
|
-
async function printIntelligenceStatus({
|
|
81
|
+
async function printIntelligenceStatus({
|
|
82
|
+
homeDir,
|
|
83
|
+
workspaceRoot,
|
|
84
|
+
profile,
|
|
85
|
+
sources,
|
|
86
|
+
json,
|
|
87
|
+
env,
|
|
88
|
+
sessionOverride
|
|
89
|
+
}) {
|
|
76
90
|
const backends = await inspectIntelligenceBackends({
|
|
77
91
|
env,
|
|
78
92
|
customProviders: profile.customProviders
|
|
@@ -81,7 +95,8 @@ async function printIntelligenceStatus({ homeDir, workspaceRoot, profile, source
|
|
|
81
95
|
const routing = resolveRoutingDecision({
|
|
82
96
|
backends,
|
|
83
97
|
profile,
|
|
84
|
-
cloudConsent: false
|
|
98
|
+
cloudConsent: false,
|
|
99
|
+
sessionOverride
|
|
85
100
|
});
|
|
86
101
|
|
|
87
102
|
const payload = {
|
|
@@ -89,6 +104,7 @@ async function printIntelligenceStatus({ homeDir, workspaceRoot, profile, source
|
|
|
89
104
|
homeDir,
|
|
90
105
|
workspaceRoot,
|
|
91
106
|
profile: buildProfileJson({ profile, sources }),
|
|
107
|
+
sessionOverride,
|
|
92
108
|
backends,
|
|
93
109
|
summary,
|
|
94
110
|
routing
|
|
@@ -119,13 +135,17 @@ async function printIntelligenceStatus({ homeDir, workspaceRoot, profile, source
|
|
|
119
135
|
return payload;
|
|
120
136
|
}
|
|
121
137
|
|
|
122
|
-
async function printIntelligenceModels({ profile, json, env }) {
|
|
138
|
+
async function printIntelligenceModels({ profile, json, env, sessionOverride }) {
|
|
123
139
|
const backends = await inspectIntelligenceBackends({
|
|
124
140
|
env,
|
|
125
141
|
customProviders: profile.customProviders
|
|
126
142
|
});
|
|
143
|
+
const filterId = sessionOverride?.preferredBackend ?? null;
|
|
144
|
+
const filtered = filterId
|
|
145
|
+
? backends.filter((backend) => backend.id === filterId)
|
|
146
|
+
: backends;
|
|
127
147
|
|
|
128
|
-
const models =
|
|
148
|
+
const models = filtered.flatMap((backend) =>
|
|
129
149
|
(backend.models ?? []).map((model) => ({
|
|
130
150
|
...model,
|
|
131
151
|
backendState: backend.state,
|
|
@@ -133,11 +153,16 @@ async function printIntelligenceModels({ profile, json, env }) {
|
|
|
133
153
|
}))
|
|
134
154
|
);
|
|
135
155
|
|
|
136
|
-
const payload = {
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
156
|
+
const payload = {
|
|
157
|
+
readOnly: true,
|
|
158
|
+
sessionOverride,
|
|
159
|
+
models,
|
|
160
|
+
backends: filtered.map((entry) => ({
|
|
161
|
+
id: entry.id,
|
|
162
|
+
state: entry.state,
|
|
163
|
+
available: entry.available
|
|
164
|
+
}))
|
|
165
|
+
};
|
|
141
166
|
|
|
142
167
|
if (json) {
|
|
143
168
|
console.log(JSON.stringify(payload, null, 2));
|
|
@@ -219,7 +244,8 @@ async function printIntelligenceRoute({
|
|
|
219
244
|
task,
|
|
220
245
|
cloudConsent,
|
|
221
246
|
json,
|
|
222
|
-
env
|
|
247
|
+
env,
|
|
248
|
+
sessionOverride
|
|
223
249
|
}) {
|
|
224
250
|
const backends = await inspectIntelligenceBackends({
|
|
225
251
|
env,
|
|
@@ -231,11 +257,13 @@ async function printIntelligenceRoute({
|
|
|
231
257
|
profile,
|
|
232
258
|
contextPack: pack,
|
|
233
259
|
task,
|
|
234
|
-
cloudConsent: Boolean(cloudConsent)
|
|
260
|
+
cloudConsent: Boolean(cloudConsent),
|
|
261
|
+
sessionOverride
|
|
235
262
|
});
|
|
236
263
|
|
|
237
264
|
const payload = {
|
|
238
265
|
readOnly: true,
|
|
266
|
+
sessionOverride,
|
|
239
267
|
routing,
|
|
240
268
|
estimatedTokens: pack.estimatedTokens,
|
|
241
269
|
evidenceUsed: pack.evidence.filter((entry) => entry.kind === "file").map((entry) => entry.path)
|
|
@@ -266,7 +294,8 @@ async function runIntelligenceAsk({
|
|
|
266
294
|
cloudConsent,
|
|
267
295
|
confirmed,
|
|
268
296
|
json,
|
|
269
|
-
env
|
|
297
|
+
env,
|
|
298
|
+
sessionOverride
|
|
270
299
|
}) {
|
|
271
300
|
if (!prompt && !task) {
|
|
272
301
|
throw new Error(`Missing prompt. Use: ${formatCliCommand("intelligence ask --prompt \"...\"")}`);
|
|
@@ -281,7 +310,8 @@ async function runIntelligenceAsk({
|
|
|
281
310
|
includePrivate,
|
|
282
311
|
cloudConsent,
|
|
283
312
|
confirmed,
|
|
284
|
-
env
|
|
313
|
+
env,
|
|
314
|
+
sessionOverride
|
|
285
315
|
});
|
|
286
316
|
|
|
287
317
|
if (json) {
|
|
@@ -289,6 +319,7 @@ async function runIntelligenceAsk({
|
|
|
289
319
|
ok: outcome.ok,
|
|
290
320
|
mode: outcome.mode,
|
|
291
321
|
diagnosticsOnly: outcome.diagnosticsOnly,
|
|
322
|
+
sessionOverride,
|
|
292
323
|
routing: outcome.routing,
|
|
293
324
|
explanation: outcome.explanation,
|
|
294
325
|
telemetry: outcome.telemetry,
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { BACKEND_IDS } from "./intelligence/index.js";
|
|
2
|
+
|
|
3
|
+
const EPHEMERAL_BACKENDS = new Set([
|
|
4
|
+
BACKEND_IDS.OLLAMA,
|
|
5
|
+
BACKEND_IDS.OPENROUTER,
|
|
6
|
+
BACKEND_IDS.OPENCODE_GO,
|
|
7
|
+
BACKEND_IDS.OPENCODE_ZEN,
|
|
8
|
+
BACKEND_IDS.OPENCODE
|
|
9
|
+
]);
|
|
10
|
+
|
|
11
|
+
const BACKEND_HINT =
|
|
12
|
+
"Use opencode-go, opencode-zen, opencode, ollama, or openrouter.";
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Resolve ephemeral CLI --backend/--model overrides.
|
|
16
|
+
* Does not read or write profiles; credentials stay in env.
|
|
17
|
+
*/
|
|
18
|
+
export function resolveSessionOverride(options = {}) {
|
|
19
|
+
if (options.intelligenceBackend != null && String(options.intelligenceBackend).trim() === "") {
|
|
20
|
+
throw new Error(`Missing --backend value. ${BACKEND_HINT}`);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const preferredBackend = options.intelligenceBackend == null
|
|
24
|
+
? null
|
|
25
|
+
: String(options.intelligenceBackend).trim();
|
|
26
|
+
const preferredModel = options.model == null || String(options.model).trim() === ""
|
|
27
|
+
? null
|
|
28
|
+
: String(options.model).trim();
|
|
29
|
+
|
|
30
|
+
if (!preferredBackend && !preferredModel) return null;
|
|
31
|
+
|
|
32
|
+
if (preferredBackend && !EPHEMERAL_BACKENDS.has(preferredBackend)) {
|
|
33
|
+
throw new Error(`Unknown --backend "${preferredBackend}". ${BACKEND_HINT}`);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return { preferredBackend, preferredModel };
|
|
37
|
+
}
|
|
@@ -8,6 +8,7 @@ export function buildControlPlaneJson(report, { cliVersion, extras = {} } = {})
|
|
|
8
8
|
overall: report.overall,
|
|
9
9
|
agents: report.agents,
|
|
10
10
|
components: report.components,
|
|
11
|
+
componentHealth: report.componentHealth ?? report.components,
|
|
11
12
|
checks: report.checks,
|
|
12
13
|
backups: report.backups,
|
|
13
14
|
nextAction: report.nextAction,
|
|
@@ -1,41 +1,23 @@
|
|
|
1
1
|
import { readFileSync } from "node:fs";
|
|
2
2
|
import { createManagedSectionBuilder } from "./component-builders.js";
|
|
3
|
+
import { normalizeCatalogDocument } from "./component-manifest.js";
|
|
3
4
|
|
|
4
5
|
const defaultCatalogUrl = new URL("../../global-template/components/catalog.json", import.meta.url);
|
|
5
6
|
|
|
6
|
-
function validateCatalogEntry(entry) {
|
|
7
|
-
const required = ["id", "label", "version", "defaultEnabled", "assetFiles"];
|
|
8
|
-
|
|
9
|
-
for (const field of required) {
|
|
10
|
-
if (entry[field] === undefined) {
|
|
11
|
-
throw new Error(`Component catalog entry is missing "${field}".`);
|
|
12
|
-
}
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
if (!Array.isArray(entry.assetFiles) || entry.assetFiles.length === 0) {
|
|
16
|
-
throw new Error(`Component "${entry.id}" must declare at least one asset file.`);
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
|
|
20
7
|
export function readComponentCatalogDocument(catalogUrl = defaultCatalogUrl) {
|
|
21
8
|
return JSON.parse(readFileSync(catalogUrl, "utf8"));
|
|
22
9
|
}
|
|
23
10
|
|
|
24
11
|
export function loadComponentCatalog(catalogUrl = defaultCatalogUrl) {
|
|
25
12
|
const catalog = readComponentCatalogDocument(catalogUrl);
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
return {
|
|
31
|
-
id: entry.id,
|
|
32
|
-
label: entry.label,
|
|
33
|
-
version: entry.version,
|
|
34
|
-
source: "bundled",
|
|
35
|
-
defaultEnabled: entry.defaultEnabled,
|
|
36
|
-
assetFiles: [...entry.assetFiles],
|
|
37
|
-
adapterHints: { ...(entry.adapterHints ?? {}) },
|
|
38
|
-
buildManagedSection: createManagedSectionBuilder(entry.id, entry)
|
|
39
|
-
};
|
|
13
|
+
const { components } = normalizeCatalogDocument(catalog, {
|
|
14
|
+
source: "bundled",
|
|
15
|
+
requireDefaultEnabled: true
|
|
40
16
|
});
|
|
17
|
+
|
|
18
|
+
return components.map((entry) => ({
|
|
19
|
+
...entry,
|
|
20
|
+
source: "bundled",
|
|
21
|
+
buildManagedSection: createManagedSectionBuilder(entry.id, entry)
|
|
22
|
+
}));
|
|
41
23
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { existsSync, lstatSync, readFileSync, realpathSync } from "node:fs";
|
|
2
2
|
import { join, resolve } from "node:path";
|
|
3
3
|
import { createWorkspaceManagedSectionBuilder } from "./component-builders.js";
|
|
4
|
+
import { normalizeCatalogDocument } from "./component-manifest.js";
|
|
4
5
|
import { isPathInside } from "./component-paths.js";
|
|
5
6
|
|
|
6
7
|
export function workspaceCatalogPath(workspaceRoot) {
|
|
@@ -24,77 +25,35 @@ export function loadWorkspaceComponentCatalog(workspaceRoot, { bundledIds = [] }
|
|
|
24
25
|
const resolvedRoot = resolve(workspaceRoot);
|
|
25
26
|
const catalog = readWorkspaceCatalogDocument(resolvedRoot);
|
|
26
27
|
const componentsRoot = join(resolvedRoot, ".harness", "components");
|
|
27
|
-
const entries = catalog
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
28
|
+
const { components: entries } = normalizeCatalogDocument(catalog, {
|
|
29
|
+
source: "workspace",
|
|
30
|
+
bundledIds,
|
|
31
|
+
requireDefaultEnabled: false
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
return entries.map((entry) => {
|
|
35
|
+
const componentDir = join(componentsRoot, entry.id);
|
|
36
|
+
|
|
37
|
+
for (const assetFile of entry.assetFiles) {
|
|
38
|
+
validateAssetPresence(assetFile, {
|
|
39
|
+
componentDir,
|
|
40
|
+
componentId: entry.id,
|
|
41
|
+
workspaceRoot: resolvedRoot
|
|
42
|
+
});
|
|
43
|
+
}
|
|
39
44
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
label: entry.label,
|
|
43
|
-
version: entry.version,
|
|
45
|
+
return {
|
|
46
|
+
...entry,
|
|
44
47
|
source: "workspace",
|
|
45
48
|
defaultEnabled: false,
|
|
46
|
-
assetFiles: [...entry.assetFiles],
|
|
47
|
-
adapterHints: { ...(entry.adapterHints ?? {}) },
|
|
48
49
|
instructions: entry.instructions ?? null,
|
|
49
50
|
workspaceRoot: resolvedRoot,
|
|
50
51
|
buildManagedSection: createWorkspaceManagedSectionBuilder(entry)
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
return components;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
function validateWorkspaceEntry(entry, { componentsRoot, workspaceRoot, bundledIds, seen }) {
|
|
58
|
-
const required = ["id", "label", "version", "assetFiles"];
|
|
59
|
-
|
|
60
|
-
for (const field of required) {
|
|
61
|
-
if (entry[field] === undefined) {
|
|
62
|
-
throw new Error(`Workspace component catalog entry is missing "${field}".`);
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
if (!Array.isArray(entry.assetFiles) || entry.assetFiles.length === 0) {
|
|
67
|
-
throw new Error(`Workspace component "${entry.id}" must declare at least one asset file.`);
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
if (bundledIds.includes(entry.id)) {
|
|
71
|
-
throw new Error(`Workspace component "${entry.id}" conflicts with a bundled component.`);
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
if (seen.has(entry.id)) {
|
|
75
|
-
throw new Error(`Duplicate workspace component id "${entry.id}".`);
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
const componentDir = join(componentsRoot, entry.id);
|
|
79
|
-
|
|
80
|
-
for (const assetFile of entry.assetFiles) {
|
|
81
|
-
validateAssetFile(assetFile, { componentDir, componentId: entry.id, workspaceRoot });
|
|
82
|
-
}
|
|
52
|
+
};
|
|
53
|
+
});
|
|
83
54
|
}
|
|
84
55
|
|
|
85
|
-
function
|
|
86
|
-
if (typeof assetFile !== "string" || assetFile.length === 0) {
|
|
87
|
-
throw new Error(`Workspace component "${componentId}" has an invalid asset path.`);
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
if (assetFile.startsWith("/") || assetFile.includes("\\") || assetFile.includes("..")) {
|
|
91
|
-
throw new Error(`Workspace component "${componentId}" asset "${assetFile}" must be a relative path without "..".`);
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
if (assetFile.endsWith("/")) {
|
|
95
|
-
throw new Error(`Workspace component "${componentId}" asset "${assetFile}" must reference a file.`);
|
|
96
|
-
}
|
|
97
|
-
|
|
56
|
+
function validateAssetPresence(assetFile, { componentDir, componentId, workspaceRoot }) {
|
|
98
57
|
const assetPath = join(componentDir, ...assetFile.split("/"));
|
|
99
58
|
const normalized = resolve(assetPath);
|
|
100
59
|
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import { resolveAdapter } from "./registry.js";
|
|
2
2
|
import { resolveComponent } from "./component-registry.js";
|
|
3
|
+
import { normalizeSddState } from "./integrations/sdd-state.js";
|
|
4
|
+
|
|
5
|
+
export const STATE_VERSION = 4;
|
|
6
|
+
export const UNSUPPORTED_STATE_VERSION = "UNSUPPORTED_STATE_VERSION";
|
|
3
7
|
|
|
4
8
|
const LEGACY_ORCHESTRATOR_COMPONENT = {
|
|
5
9
|
id: "orchestrator",
|
|
@@ -7,17 +11,33 @@ const LEGACY_ORCHESTRATOR_COMPONENT = {
|
|
|
7
11
|
managedTargets: []
|
|
8
12
|
};
|
|
9
13
|
|
|
14
|
+
/** Fail closed on future state versions — never silently downgrade. */
|
|
15
|
+
export function assertCompatibleStateVersion(state) {
|
|
16
|
+
const version = state?.stateVersion;
|
|
17
|
+
if (typeof version === "number" && version > STATE_VERSION) {
|
|
18
|
+
const error = new Error(
|
|
19
|
+
`Unsupported stateVersion ${version}; this runtime supports up to ${STATE_VERSION}.`
|
|
20
|
+
);
|
|
21
|
+
error.code = UNSUPPORTED_STATE_VERSION;
|
|
22
|
+
throw error;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** Normalize any compatible v3/v4 (or legacy) state into explicit stateVersion 4. Idempotent for v4. */
|
|
10
27
|
export function normalizeGlobalState(state) {
|
|
11
28
|
if (!state) return null;
|
|
29
|
+
assertCompatibleStateVersion(state);
|
|
12
30
|
|
|
13
31
|
const adapters = state.adapters ?? migrateLegacyAgents(state.agents ?? []);
|
|
14
32
|
const components = state.components ?? migrateLegacyComponents(state, adapters);
|
|
15
33
|
|
|
16
34
|
return {
|
|
17
35
|
...state,
|
|
36
|
+
stateVersion: STATE_VERSION,
|
|
18
37
|
adapters,
|
|
19
38
|
agents: toLegacyAgents(adapters),
|
|
20
|
-
components
|
|
39
|
+
components,
|
|
40
|
+
sdd: normalizeSddState(state.sdd)
|
|
21
41
|
};
|
|
22
42
|
}
|
|
23
43
|
|
package/src/global/state.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { existsSync } from "node:fs";
|
|
2
2
|
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
3
3
|
import { dirname } from "node:path";
|
|
4
|
-
import { normalizeGlobalState } from "./state-migration.js";
|
|
4
|
+
import { normalizeGlobalState, STATE_VERSION, UNSUPPORTED_STATE_VERSION } from "./state-migration.js";
|
|
5
|
+
import { normalizeSddState } from "./integrations/sdd-state.js";
|
|
5
6
|
|
|
6
|
-
export
|
|
7
|
+
export { STATE_VERSION, UNSUPPORTED_STATE_VERSION };
|
|
7
8
|
|
|
8
9
|
export async function readGlobalState(statePath) {
|
|
9
10
|
if (!existsSync(statePath)) return null;
|
|
@@ -11,14 +12,16 @@ export async function readGlobalState(statePath) {
|
|
|
11
12
|
try {
|
|
12
13
|
const raw = JSON.parse(await readFile(statePath, "utf8"));
|
|
13
14
|
return normalizeGlobalState(raw);
|
|
14
|
-
} catch {
|
|
15
|
+
} catch (error) {
|
|
16
|
+
if (error?.code === UNSUPPORTED_STATE_VERSION) throw error;
|
|
15
17
|
return null;
|
|
16
18
|
}
|
|
17
19
|
}
|
|
18
20
|
|
|
19
21
|
export async function writeGlobalState(statePath, state) {
|
|
22
|
+
const normalized = normalizeGlobalState(state) ?? state;
|
|
20
23
|
await mkdir(dirname(statePath), { recursive: true });
|
|
21
|
-
await writeFile(statePath, `${JSON.stringify(
|
|
24
|
+
await writeFile(statePath, `${JSON.stringify(normalized, null, 2)}\n`, "utf8");
|
|
22
25
|
}
|
|
23
26
|
|
|
24
27
|
export function createGlobalState({
|
|
@@ -28,13 +31,14 @@ export function createGlobalState({
|
|
|
28
31
|
components = [],
|
|
29
32
|
coreFiles,
|
|
30
33
|
backups,
|
|
31
|
-
installedAt
|
|
34
|
+
installedAt,
|
|
35
|
+
sdd
|
|
32
36
|
}) {
|
|
33
37
|
const now = new Date().toISOString();
|
|
34
38
|
const normalizedAdapters = adapters.map((entry) => ({ ...entry }));
|
|
35
39
|
const normalizedComponents = components.map((entry) => ({ ...entry }));
|
|
36
40
|
|
|
37
|
-
return {
|
|
41
|
+
return normalizeGlobalState({
|
|
38
42
|
stateVersion: STATE_VERSION,
|
|
39
43
|
packageName,
|
|
40
44
|
cliVersion,
|
|
@@ -45,6 +49,7 @@ export function createGlobalState({
|
|
|
45
49
|
agents: normalizedAdapters.map(({ id, configFile, present }) => ({ id, configFile, present })),
|
|
46
50
|
components: normalizedComponents,
|
|
47
51
|
coreFiles,
|
|
48
|
-
backups
|
|
49
|
-
|
|
52
|
+
backups,
|
|
53
|
+
sdd: normalizeSddState(sdd)
|
|
54
|
+
});
|
|
50
55
|
}
|
package/src/global/status.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { listBackupSnapshots } from "./backups.js";
|
|
2
|
+
import { buildComponentHealthEntries } from "./component-health.js";
|
|
2
3
|
import { runGlobalDoctorChecks } from "./global-doctor.js";
|
|
3
4
|
import { buildEffectivePolicy } from "./policy.js";
|
|
4
5
|
import { harnessHomePaths } from "./paths.js";
|
|
@@ -20,16 +21,8 @@ export async function buildStatusReport(homeDir, { packageRoot, workspaceRoot =
|
|
|
20
21
|
managed: installedAgentIds.has(id)
|
|
21
22
|
}));
|
|
22
23
|
|
|
23
|
-
const components = (state?.components ?? []
|
|
24
|
-
|
|
25
|
-
const status = summarizeCheckStatuses(related);
|
|
26
|
-
return {
|
|
27
|
-
id: entry.id,
|
|
28
|
-
version: entry.version,
|
|
29
|
-
source: entry.source ?? "bundled",
|
|
30
|
-
status
|
|
31
|
-
};
|
|
32
|
-
});
|
|
24
|
+
const components = buildComponentHealthEntries(state?.components ?? [], doctor.checks)
|
|
25
|
+
.map(({ checks, ...entry }) => entry);
|
|
33
26
|
|
|
34
27
|
const counts = {
|
|
35
28
|
ok: doctor.checks.filter((check) => check.status === "ok").length,
|
|
@@ -48,6 +41,7 @@ export async function buildStatusReport(homeDir, { packageRoot, workspaceRoot =
|
|
|
48
41
|
state,
|
|
49
42
|
agents,
|
|
50
43
|
components,
|
|
44
|
+
componentHealth: doctor.componentHealth,
|
|
51
45
|
checks: doctor.checks,
|
|
52
46
|
backups: backups.length,
|
|
53
47
|
counts,
|
|
@@ -58,13 +52,6 @@ export async function buildStatusReport(homeDir, { packageRoot, workspaceRoot =
|
|
|
58
52
|
};
|
|
59
53
|
}
|
|
60
54
|
|
|
61
|
-
function summarizeCheckStatuses(checks) {
|
|
62
|
-
if (checks.length === 0) return "ok";
|
|
63
|
-
if (checks.some((check) => check.status === "missing")) return "missing";
|
|
64
|
-
if (checks.some((check) => check.status === "stale")) return "stale";
|
|
65
|
-
return "ok";
|
|
66
|
-
}
|
|
67
|
-
|
|
68
55
|
function resolveOverallStatus({ state, doctor }) {
|
|
69
56
|
if (!state) return "missing";
|
|
70
57
|
if (doctor.hasDrift) return "drift";
|
package/src/global/sync.js
CHANGED
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
shouldPromptApplyConfirmation
|
|
7
7
|
} from "./apply-confirmation.js";
|
|
8
8
|
import { buildDiffReport } from "./diff.js";
|
|
9
|
+
import { needsManagedRepair } from "./governance-repair.js";
|
|
9
10
|
import { harnessHomePaths } from "./paths.js";
|
|
10
11
|
import { printManagedPreflight, shouldShowPreflight, summarizeDiffPreflight } from "./preflight.js";
|
|
11
12
|
import { loadConsentAudit } from "./policy.js";
|
|
@@ -42,11 +43,7 @@ export async function runHarnessSync({
|
|
|
42
43
|
}
|
|
43
44
|
|
|
44
45
|
const preReport = await buildStatusReport(homeDir, { packageRoot, workspaceRoot });
|
|
45
|
-
|
|
46
|
-
|| preReport.counts.missing > 0
|
|
47
|
-
|| preReport.counts.stale > 0;
|
|
48
|
-
|
|
49
|
-
if (!needsRepair) {
|
|
46
|
+
if (!needsManagedRepair(preReport)) {
|
|
50
47
|
return {
|
|
51
48
|
action: "noop",
|
|
52
49
|
wrote: false,
|