@kal-elsam/kairo-runtime 0.11.0 → 0.13.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/global-template/components/agent-skills/LICENSE +21 -0
- package/global-template/components/agent-skills/PROVENANCE.md +26 -0
- package/global-template/components/agent-skills/skills/context-engineering/SKILL.md +289 -0
- package/global-template/components/agent-skills/skills/frontend-ui-engineering/SKILL.md +328 -0
- package/global-template/components/agent-skills/skills/observability-and-instrumentation/SKILL.md +203 -0
- package/global-template/components/agent-skills/skills/performance-optimization/SKILL.md +396 -0
- package/global-template/components/agent-skills/skills/source-driven-development/SKILL.md +194 -0
- package/global-template/components/catalog.json +29 -0
- package/package.json +5 -2
- package/scripts/cockpit-smoke.mjs +2 -1
- package/src/cli.js +136 -8
- package/src/global/component-builders.js +3 -1
- package/src/global/components/agent-skills.js +27 -0
- package/src/global/ink/cockpit-control-center.js +107 -4
- package/src/global/ink/cockpit-scan.js +20 -2
- package/src/global/ink/ecosystem-updates-display.js +37 -0
- package/src/global/ink/launch-input.js +32 -1
- package/src/global/ink/obsidian-vault-display.js +37 -0
- package/src/global/ink/orchestrator-app.js +2 -1
- package/src/global/ink/orchestrator-state.js +17 -2
- package/src/global/ink/system-resources-display.js +109 -0
- package/src/global/ink/use-orchestrator-data.js +40 -4
- package/src/global/ink/ux/live-overview.js +11 -1
- package/src/global/mcp/kairo-mcp.js +230 -0
- package/src/global/observability/build-companion-snapshot.js +302 -0
- package/src/global/observability/build-observability-snapshot.js +24 -0
- package/src/global/observability/ecosystem-updates.js +224 -0
- package/src/global/observability/gentle-bundle-export.js +71 -0
- package/src/global/observability/gentle-bundle-import.js +122 -0
- package/src/global/observability/gentle-probe.js +155 -0
- package/src/global/observability/graphify-ops.js +133 -0
- package/src/global/observability/graphify-parse-cache.js +90 -0
- package/src/global/observability/graphify-probe.js +185 -0
- package/src/global/observability/hermes-activity.js +163 -0
- package/src/global/observability/hermes-probe.js +171 -0
- package/src/global/observability/index.js +124 -0
- package/src/global/observability/obsidian-knowledge-preview.js +214 -0
- package/src/global/observability/obsidian-knowledge-views.js +227 -0
- package/src/global/observability/obsidian-publisher.js +181 -0
- package/src/global/observability/obsidian-status.js +76 -0
- package/src/global/observability/obsidian-vault.js +259 -0
- package/src/global/observability/passive-snapshot-flight.js +93 -0
- package/src/global/observability/probe-contract.js +38 -0
- package/src/global/observability/probe-registry.js +30 -0
- package/src/global/observability/resource-advisor.js +71 -0
- package/src/global/observability/system-resources.js +171 -0
- package/src/global/runtime/alerts/alert-cli.js +31 -0
- package/src/global/runtime/alerts/alert-store.js +29 -6
- package/src/global/runtime/alerts/alert-validate.js +25 -1
- package/src/global/runtime/alerts/controlled-alert-actions.js +56 -0
- package/src/global/runtime/execution-adapters/claude.js +2 -1
- package/src/global/runtime/execution-adapters/codex.js +2 -1
- package/src/global/runtime/execution-adapters/create-execution-adapter.js +3 -14
- package/src/global/runtime/execution-adapters/cursor.js +2 -1
- package/src/global/runtime/execution-adapters/opencode.js +2 -1
- package/src/global/runtime/execution-adapters/pi.js +2 -1
- package/src/global/runtime/review/index.js +1 -1
- package/src/global/runtime/review/review-cli.js +113 -3
- package/src/global/runtime/review/review-git.js +142 -11
- package/src/global/runtime/review/review-patch.js +2 -0
- package/src/global/runtime/review/review-receipts.js +12 -7
- package/src/global/runtime/review/review-runner.js +2 -2
- package/src/global/runtime/review/review-types.js +8 -5
- package/src/global/runtime/review/review-validate.js +5 -1
- package/src/global/runtime/run-cli.js +2 -0
- package/src/global/runtime/run-manager.js +39 -18
- package/src/global/runtime/run-permissions.js +231 -0
- package/src/global/runtime/run-profile.js +2 -0
- package/src/global/runtime/run-supervisor.js +77 -37
- package/src/global/runtime/run-types.js +2 -0
- package/src/global/updates-cli.js +41 -0
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import { isAbsolute } from "node:path";
|
|
2
|
+
import {
|
|
3
|
+
parseVersionFromOutput,
|
|
4
|
+
probeCommand as defaultProbeCommand
|
|
5
|
+
} from "../cli-probe.js";
|
|
6
|
+
import { normalizeProbeResult } from "./probe-contract.js";
|
|
7
|
+
|
|
8
|
+
/** Surfaces recognized from `hermes --help` only — never executed by this probe. */
|
|
9
|
+
export const HERMES_DIAGNOSTIC_SURFACES = Object.freeze(["version", "doctor", "status"]);
|
|
10
|
+
export const HERMES_MANDATORY_SURFACES = Object.freeze(["version", "doctor"]);
|
|
11
|
+
|
|
12
|
+
const WHICH_TIMEOUT_MS = 3000;
|
|
13
|
+
const VERSION_TIMEOUT_MS = 8000;
|
|
14
|
+
const HELP_TIMEOUT_MS = 8000;
|
|
15
|
+
const MAX_PROBE_BYTES = 65_536;
|
|
16
|
+
|
|
17
|
+
function result(partial) {
|
|
18
|
+
return normalizeProbeResult({
|
|
19
|
+
id: "hermes", version: null, contractCompatible: null,
|
|
20
|
+
diagnostics: [], evidence: [], error: null, ...partial
|
|
21
|
+
}, "hermes");
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function boundText(raw, maxBytes = MAX_PROBE_BYTES) {
|
|
25
|
+
const text = String(raw ?? "");
|
|
26
|
+
if (Buffer.byteLength(text, "utf8") <= maxBytes) return text;
|
|
27
|
+
return Buffer.from(text, "utf8").subarray(0, maxBytes).toString("utf8");
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function whichAbsolutePath(command, env, run = defaultProbeCommand) {
|
|
31
|
+
const which = run("which", [command], { env, timeoutMs: WHICH_TIMEOUT_MS });
|
|
32
|
+
if (!which?.ok) return "";
|
|
33
|
+
const path = String(which.stdout ?? "").trim().split(/\r?\n/)[0] ?? "";
|
|
34
|
+
return isAbsolute(path) ? path : "";
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** Resolve absolute Hermes binary; never return a bare command name. */
|
|
38
|
+
export function resolveHermesBinaryPath(command = "hermes", env = process.env, {
|
|
39
|
+
whichCommand,
|
|
40
|
+
probeCommand = defaultProbeCommand
|
|
41
|
+
} = {}) {
|
|
42
|
+
const resolver = typeof whichCommand === "function"
|
|
43
|
+
? whichCommand
|
|
44
|
+
: (cmd, e) => whichAbsolutePath(cmd, e, probeCommand);
|
|
45
|
+
const resolved = resolver(command, env) || null;
|
|
46
|
+
return resolved && isAbsolute(resolved) ? resolved : null;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** Presence means the CLI *declares* the surface — not that Kairo may run it. */
|
|
50
|
+
export function detectHermesDiagnosticSurfaces(helpText) {
|
|
51
|
+
const text = boundText(helpText);
|
|
52
|
+
const found = Object.create(null);
|
|
53
|
+
for (const name of HERMES_DIAGNOSTIC_SURFACES) {
|
|
54
|
+
found[name] = new RegExp(`(?:^|[\\s,{|/])${name}(?=[\\s,}|/-]|$)`, "m").test(text);
|
|
55
|
+
}
|
|
56
|
+
return Object.freeze(found);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function softError(label, kind, evidence, extra = {}) {
|
|
60
|
+
return result({
|
|
61
|
+
state: "error",
|
|
62
|
+
diagnostics: [`hermes ${label} ${kind === "timeout" ? "timed out" : "failed"}`],
|
|
63
|
+
error: kind === "timeout" ? "timeout" : kind === "exit" ? `exit ${extra.code}` : "spawn_error",
|
|
64
|
+
evidence,
|
|
65
|
+
...extra.fields
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function spawnFailure(label, probeResult, evidence, extra = {}) {
|
|
70
|
+
if (probeResult?.timedOut) return softError(label, "timeout", evidence, extra);
|
|
71
|
+
if (probeResult?.error) return softError(label, "spawn", evidence, extra);
|
|
72
|
+
if (probeResult?.status !== 0) {
|
|
73
|
+
return softError(label, "exit", evidence, { ...extra, code: probeResult?.status ?? "unknown" });
|
|
74
|
+
}
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** which: preserve timeout/spawn_error; exit/empty → missing (not failure). */
|
|
79
|
+
function whichFailure(probeResult, evidence) {
|
|
80
|
+
if (probeResult?.timedOut) return softError("which", "timeout", evidence);
|
|
81
|
+
if (probeResult?.error) return softError("which", "spawn", evidence);
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function runProbe(probeCommand, cmd, args, opts) {
|
|
86
|
+
try { return { ok: true, value: probeCommand(cmd, args, opts) }; }
|
|
87
|
+
catch { return { ok: false }; }
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export async function probeHermes({
|
|
91
|
+
env = process.env,
|
|
92
|
+
cwd = process.cwd(),
|
|
93
|
+
whichCommand,
|
|
94
|
+
probeCommand = defaultProbeCommand
|
|
95
|
+
} = {}) {
|
|
96
|
+
const noBinary = [{ kind: "binary", path: null }];
|
|
97
|
+
let path;
|
|
98
|
+
|
|
99
|
+
if (typeof whichCommand === "function") {
|
|
100
|
+
try { path = resolveHermesBinaryPath("hermes", env, { whichCommand, probeCommand }); }
|
|
101
|
+
catch { return softError("which", "spawn", noBinary); }
|
|
102
|
+
} else {
|
|
103
|
+
const inv = runProbe(probeCommand, "which", ["hermes"], { env, timeoutMs: WHICH_TIMEOUT_MS });
|
|
104
|
+
if (!inv.ok) return softError("which", "spawn", noBinary);
|
|
105
|
+
const fail = whichFailure(inv.value, noBinary);
|
|
106
|
+
if (fail) return fail;
|
|
107
|
+
const candidate = String(inv.value?.stdout ?? "").trim().split(/\r?\n/)[0] ?? "";
|
|
108
|
+
path = inv.value?.ok && isAbsolute(candidate) ? candidate : null;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (!path) {
|
|
112
|
+
return result({
|
|
113
|
+
state: "missing",
|
|
114
|
+
diagnostics: [
|
|
115
|
+
"hermes absolute binary not resolved. Install Hermes Agent separately, then re-run the probe."
|
|
116
|
+
],
|
|
117
|
+
evidence: noBinary
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const evidence = [{ kind: "binary", path }];
|
|
122
|
+
const versionInv = runProbe(probeCommand, path, ["--version"], {
|
|
123
|
+
cwd, env, timeoutMs: VERSION_TIMEOUT_MS
|
|
124
|
+
});
|
|
125
|
+
if (!versionInv.ok) return softError("--version", "spawn", evidence);
|
|
126
|
+
const versionFail = spawnFailure("--version", versionInv.value, evidence);
|
|
127
|
+
if (versionFail) return versionFail;
|
|
128
|
+
const version = parseVersionFromOutput(boundText(versionInv.value.stdout));
|
|
129
|
+
evidence.push({ kind: "version", version, ok: true });
|
|
130
|
+
if (!version) {
|
|
131
|
+
return result({
|
|
132
|
+
state: "incompatible", contractCompatible: false, evidence,
|
|
133
|
+
diagnostics: ["hermes --version did not yield a parseable version"]
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const helpInv = runProbe(probeCommand, path, ["--help"], {
|
|
138
|
+
cwd, env, timeoutMs: HELP_TIMEOUT_MS
|
|
139
|
+
});
|
|
140
|
+
if (!helpInv.ok) return softError("--help", "spawn", evidence, { fields: { version } });
|
|
141
|
+
const helpFail = spawnFailure("--help", helpInv.value, evidence, { fields: { version } });
|
|
142
|
+
if (helpFail) return helpFail;
|
|
143
|
+
|
|
144
|
+
const surfaces = detectHermesDiagnosticSurfaces(helpInv.value.stdout);
|
|
145
|
+
// Capability availability only — surfaces were not executed.
|
|
146
|
+
evidence.push({ kind: "diagnostic_surfaces", surfaces: { ...surfaces }, executed: false });
|
|
147
|
+
|
|
148
|
+
const missingMandatory = HERMES_MANDATORY_SURFACES.filter((name) => !surfaces[name]);
|
|
149
|
+
if (missingMandatory.length) {
|
|
150
|
+
return result({
|
|
151
|
+
state: "incompatible", version, contractCompatible: false, evidence,
|
|
152
|
+
diagnostics: missingMandatory.map((name) => `missing mandatory surface in --help: ${name}`)
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
return result({
|
|
157
|
+
state: "available", version, contractCompatible: true, evidence,
|
|
158
|
+
diagnostics: surfaces.status ? [] : ["optional surface absent in --help: status"]
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export function createHermesProbe(deps = {}) {
|
|
163
|
+
return {
|
|
164
|
+
id: "hermes",
|
|
165
|
+
declaredEvents: Object.freeze([]),
|
|
166
|
+
declaredActions: Object.freeze([]),
|
|
167
|
+
async probe(context = {}) {
|
|
168
|
+
return probeHermes({ ...deps, ...context, env: context.env ?? deps.env ?? process.env });
|
|
169
|
+
}
|
|
170
|
+
};
|
|
171
|
+
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { createGentleProbe } from "./gentle-probe.js";
|
|
2
|
+
import { createGraphifyProbe } from "./graphify-probe.js";
|
|
3
|
+
import { createHermesProbe } from "./hermes-probe.js";
|
|
4
|
+
import { getObservabilityProbe, registerObservabilityProbe } from "./probe-registry.js";
|
|
5
|
+
|
|
6
|
+
export {
|
|
7
|
+
OBSERVABILITY_PROBE_STATES,
|
|
8
|
+
assertObservabilityProbeContract,
|
|
9
|
+
normalizeProbeResult
|
|
10
|
+
} from "./probe-contract.js";
|
|
11
|
+
export {
|
|
12
|
+
registerObservabilityProbe,
|
|
13
|
+
getObservabilityProbe,
|
|
14
|
+
listObservabilityProbes,
|
|
15
|
+
resetObservabilityProbesForTests
|
|
16
|
+
} from "./probe-registry.js";
|
|
17
|
+
export { buildObservabilitySnapshot } from "./build-observability-snapshot.js";
|
|
18
|
+
export {
|
|
19
|
+
PASSIVE_SNAPSHOT_TTL_MS,
|
|
20
|
+
PASSIVE_SNAPSHOT_MAX_ENTRIES,
|
|
21
|
+
buildPassiveSnapshotKey,
|
|
22
|
+
resetPassiveSnapshotFlightForTests,
|
|
23
|
+
passiveSnapshotInFlightSizeForTests,
|
|
24
|
+
runPassiveObservabilitySnapshot
|
|
25
|
+
} from "./passive-snapshot-flight.js";
|
|
26
|
+
export {
|
|
27
|
+
SUPPORTED_PROTOCOL, SUPPORTED_SCHEMA, SUPPORTED_CONTRACT,
|
|
28
|
+
SUPPORTED_MANDATORY_FEATURES, evaluateGentleCapabilities, probeGentle, createGentleProbe,
|
|
29
|
+
resolveGentleBinaryPath
|
|
30
|
+
} from "./gentle-probe.js";
|
|
31
|
+
export { exportGentleReviewBundle, resolveNegotiatedGentleBinary } from "./gentle-bundle-export.js";
|
|
32
|
+
export { importGentleReviewBundle } from "./gentle-bundle-import.js";
|
|
33
|
+
export {
|
|
34
|
+
inspectGraphArtifact, assertGraphInsideWorkspace,
|
|
35
|
+
resolveGraphifyBinaryPath, resolveGitHeadSha, scrubGitOverrideEnv,
|
|
36
|
+
probeGraphify, createGraphifyProbe
|
|
37
|
+
} from "./graphify-probe.js";
|
|
38
|
+
export {
|
|
39
|
+
GRAPHIFY_PARSE_TTL_MS,
|
|
40
|
+
GRAPHIFY_PARSE_MAX_ENTRIES,
|
|
41
|
+
buildGraphifyParseIdentity,
|
|
42
|
+
resetGraphifyParseCacheForTests,
|
|
43
|
+
inspectGraphArtifactCached
|
|
44
|
+
} from "./graphify-parse-cache.js";
|
|
45
|
+
export { runGraphifyOp, runGraphifyCli } from "./graphify-ops.js";
|
|
46
|
+
export {
|
|
47
|
+
HERMES_DIAGNOSTIC_SURFACES, HERMES_MANDATORY_SURFACES,
|
|
48
|
+
detectHermesDiagnosticSurfaces, resolveHermesBinaryPath,
|
|
49
|
+
probeHermes, createHermesProbe
|
|
50
|
+
} from "./hermes-probe.js";
|
|
51
|
+
export {
|
|
52
|
+
DEFAULT_HERMES_API_URL,
|
|
53
|
+
HERMES_ACTIVITY_LIMIT_DEFAULT, HERMES_ACTIVITY_LIMIT_MAX,
|
|
54
|
+
HERMES_ACTIVITY_TIMEOUT_MS, HERMES_ACTIVE_WINDOW_MS,
|
|
55
|
+
assertHermesLoopbackUrl, capabilitiesAdvertiseSessionsList,
|
|
56
|
+
normalizeHermesSession, loadHermesActivity
|
|
57
|
+
} from "./hermes-activity.js";
|
|
58
|
+
export {
|
|
59
|
+
SYSTEM_RESOURCES_TIMEOUT_MS, PROCESS_ALLOWLIST,
|
|
60
|
+
parseProcessTable, loadSystemResources
|
|
61
|
+
} from "./system-resources.js";
|
|
62
|
+
export { recommendSystemResources } from "./resource-advisor.js";
|
|
63
|
+
export {
|
|
64
|
+
ECOSYSTEM_UPDATES_CACHE_MS,
|
|
65
|
+
ECOSYSTEM_UPDATES_TIMEOUT_MS,
|
|
66
|
+
AGENT_SKILLS_PINNED_REV,
|
|
67
|
+
parseGentleUpdateOutput,
|
|
68
|
+
parseHermesUpdateCheck,
|
|
69
|
+
loadEcosystemUpdates
|
|
70
|
+
} from "./ecosystem-updates.js";
|
|
71
|
+
export {
|
|
72
|
+
KAIRO_VAULT_SUBDIR,
|
|
73
|
+
EXCLUDED_DIR_NAMES,
|
|
74
|
+
normalizeVaultPath,
|
|
75
|
+
isExcludedDirName,
|
|
76
|
+
isSecretBasename,
|
|
77
|
+
isAllowedKairoNoteName,
|
|
78
|
+
assertInsideKairoRoot,
|
|
79
|
+
resolveKairoNotePath,
|
|
80
|
+
inspectObsidianVault
|
|
81
|
+
} from "./obsidian-vault.js";
|
|
82
|
+
export {
|
|
83
|
+
formatKnowledgeFrontmatter,
|
|
84
|
+
renderDecisionMarkdown,
|
|
85
|
+
renderArchitectureMarkdown,
|
|
86
|
+
buildObsidianKnowledgePreview,
|
|
87
|
+
loadObsidianKnowledgePreview
|
|
88
|
+
} from "./obsidian-knowledge-preview.js";
|
|
89
|
+
export {
|
|
90
|
+
KAIRO_MANAGED_FRONTMATTER,
|
|
91
|
+
BACKUP_DIR_NAME,
|
|
92
|
+
hasConsent,
|
|
93
|
+
classifyNoteWrite,
|
|
94
|
+
planObsidianPublish,
|
|
95
|
+
publishObsidianProposals
|
|
96
|
+
} from "./obsidian-publisher.js";
|
|
97
|
+
export {
|
|
98
|
+
KAIRO_VIEW_KINDS,
|
|
99
|
+
parseKnowledgeFrontmatter,
|
|
100
|
+
extractWikilinks,
|
|
101
|
+
buildObsidianKnowledgeViews,
|
|
102
|
+
buildKnowledgeIndexProposals,
|
|
103
|
+
loadObsidianKnowledgeViews
|
|
104
|
+
} from "./obsidian-knowledge-views.js";
|
|
105
|
+
export {
|
|
106
|
+
emptyObsidianVaultStatus,
|
|
107
|
+
summarizeObsidianVaultStatus,
|
|
108
|
+
loadObsidianVaultStatus
|
|
109
|
+
} from "./obsidian-status.js";
|
|
110
|
+
export {
|
|
111
|
+
SOFT_LINK_WINDOW_MS,
|
|
112
|
+
parseCompanionTimestamp,
|
|
113
|
+
resolveRunTimestamp,
|
|
114
|
+
resolveReviewTimestamp,
|
|
115
|
+
softLinkReviewToRun,
|
|
116
|
+
summarizeCompanionProbes,
|
|
117
|
+
buildCompanionSnapshot
|
|
118
|
+
} from "./build-companion-snapshot.js";
|
|
119
|
+
|
|
120
|
+
export function ensureObservabilityProbesRegistered() {
|
|
121
|
+
if (!getObservabilityProbe("gentle")) registerObservabilityProbe(createGentleProbe());
|
|
122
|
+
if (!getObservabilityProbe("graphify")) registerObservabilityProbe(createGraphifyProbe());
|
|
123
|
+
if (!getObservabilityProbe("hermes")) registerObservabilityProbe(createHermesProbe());
|
|
124
|
+
}
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
import { resolveKairoNotePath } from "./obsidian-vault.js";
|
|
2
|
+
|
|
3
|
+
const MAX_PROPOSALS = 40;
|
|
4
|
+
const TITLE_MAX = 80;
|
|
5
|
+
|
|
6
|
+
function envelope(partial = {}) {
|
|
7
|
+
return {
|
|
8
|
+
state: "error",
|
|
9
|
+
proposals: [],
|
|
10
|
+
diagnostics: [],
|
|
11
|
+
error: null,
|
|
12
|
+
generatedAt: null,
|
|
13
|
+
...partial
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function scrubTitle(raw, fallback = "untitled") {
|
|
18
|
+
const text = String(raw ?? "")
|
|
19
|
+
.replace(/[\r\n\t]+/g, " ")
|
|
20
|
+
.replace(/[\[\]#|\\/]+/g, " ")
|
|
21
|
+
.trim()
|
|
22
|
+
.slice(0, TITLE_MAX);
|
|
23
|
+
return text || fallback;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function slugify(title) {
|
|
27
|
+
return scrubTitle(title, "note")
|
|
28
|
+
.toLowerCase()
|
|
29
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
30
|
+
.replace(/^-+|-+$/g, "")
|
|
31
|
+
.slice(0, 48) || "note";
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** Stable YAML-ish frontmatter — no nested objects; values are scalars only. */
|
|
35
|
+
export function formatKnowledgeFrontmatter(fields = {}) {
|
|
36
|
+
const lines = ["---"];
|
|
37
|
+
for (const key of Object.keys(fields).sort()) {
|
|
38
|
+
const value = fields[key];
|
|
39
|
+
if (value == null || value === "") continue;
|
|
40
|
+
const safe = String(value).replace(/[\r\n]+/g, " ").replace(/"/g, "'");
|
|
41
|
+
lines.push(`${key}: "${safe}"`);
|
|
42
|
+
}
|
|
43
|
+
lines.push("---", "");
|
|
44
|
+
return lines.join("\n");
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function renderDecisionMarkdown(entry, { generatedAt } = {}) {
|
|
48
|
+
const title = scrubTitle(entry?.title ?? entry?.id, "decision");
|
|
49
|
+
const id = scrubTitle(entry?.id ?? slugify(title), slugify(title));
|
|
50
|
+
const body = String(entry?.body ?? entry?.content ?? "").trim();
|
|
51
|
+
const fm = formatKnowledgeFrontmatter({
|
|
52
|
+
kairo_kind: "decision",
|
|
53
|
+
kairo_id: id,
|
|
54
|
+
source: "engram-export",
|
|
55
|
+
generated_at: generatedAt ?? null,
|
|
56
|
+
title
|
|
57
|
+
});
|
|
58
|
+
const wiki = `[[decisions/${slugify(title)}]]`;
|
|
59
|
+
return {
|
|
60
|
+
relativePath: `decisions/${slugify(title)}.md`,
|
|
61
|
+
title,
|
|
62
|
+
provenance: { system: "engram", kind: "decision", id },
|
|
63
|
+
markdown: `${fm}# ${title}\n\n${body || "_No body provided._"}\n\n---\nSource: Engram export · ${wiki}\n`
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function renderArchitectureMarkdown(entry, { generatedAt } = {}) {
|
|
68
|
+
const title = scrubTitle(entry?.title ?? entry?.name ?? entry?.id, "architecture");
|
|
69
|
+
const id = scrubTitle(entry?.id ?? slugify(title), slugify(title));
|
|
70
|
+
const detail = String(entry?.detail ?? entry?.summary ?? "").trim();
|
|
71
|
+
const links = Array.isArray(entry?.related)
|
|
72
|
+
? entry.related.map((r) => `- [[architecture/${slugify(r)}]]`).join("\n")
|
|
73
|
+
: "";
|
|
74
|
+
const fm = formatKnowledgeFrontmatter({
|
|
75
|
+
kairo_kind: "architecture",
|
|
76
|
+
kairo_id: id,
|
|
77
|
+
source: "graphify-export",
|
|
78
|
+
generated_at: generatedAt ?? null,
|
|
79
|
+
title
|
|
80
|
+
});
|
|
81
|
+
return {
|
|
82
|
+
relativePath: `architecture/${slugify(title)}.md`,
|
|
83
|
+
title,
|
|
84
|
+
provenance: { system: "graphify", kind: "architecture", id },
|
|
85
|
+
markdown: `${fm}# ${title}\n\n${detail || "_No summary provided._"}\n${links ? `\n## Related\n${links}\n` : ""}`
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Pure composer — accepts already-exported records only.
|
|
91
|
+
* Never opens Engram/Graphify internal DBs or vault files.
|
|
92
|
+
*/
|
|
93
|
+
export function buildObsidianKnowledgePreview({
|
|
94
|
+
decisions = [],
|
|
95
|
+
architecture = [],
|
|
96
|
+
generatedAt = new Date().toISOString(),
|
|
97
|
+
maxProposals = MAX_PROPOSALS,
|
|
98
|
+
kairoRoot = "/virtual/Kairo"
|
|
99
|
+
} = {}) {
|
|
100
|
+
const diagnostics = [];
|
|
101
|
+
const proposals = [];
|
|
102
|
+
|
|
103
|
+
const push = (draft) => {
|
|
104
|
+
if (proposals.length >= maxProposals) return;
|
|
105
|
+
const gate = resolveKairoNotePath(kairoRoot, draft.relativePath);
|
|
106
|
+
if (!gate.ok) {
|
|
107
|
+
diagnostics.push(`rejected ${draft.relativePath}: ${gate.reason}`);
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
proposals.push({
|
|
111
|
+
relativePath: draft.relativePath,
|
|
112
|
+
title: draft.title,
|
|
113
|
+
markdown: draft.markdown,
|
|
114
|
+
provenance: draft.provenance,
|
|
115
|
+
absolutePath: gate.path
|
|
116
|
+
});
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
for (const entry of decisions ?? []) {
|
|
120
|
+
if (entry == null || typeof entry !== "object") {
|
|
121
|
+
diagnostics.push("skipped malformed decision");
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
push(renderDecisionMarkdown(entry, { generatedAt }));
|
|
125
|
+
}
|
|
126
|
+
for (const entry of architecture ?? []) {
|
|
127
|
+
if (entry == null || typeof entry !== "object") {
|
|
128
|
+
diagnostics.push("skipped malformed architecture");
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
push(renderArchitectureMarkdown(entry, { generatedAt }));
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (proposals.length === 0 && diagnostics.length === 0) {
|
|
135
|
+
return envelope({
|
|
136
|
+
state: "empty",
|
|
137
|
+
proposals: [],
|
|
138
|
+
diagnostics: ["no export records provided"],
|
|
139
|
+
generatedAt,
|
|
140
|
+
error: null
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
return envelope({
|
|
145
|
+
state: proposals.length > 0 ? "available" : "partial",
|
|
146
|
+
proposals,
|
|
147
|
+
diagnostics,
|
|
148
|
+
generatedAt,
|
|
149
|
+
error: null
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Load preview via injectable export adapters — never vault writes.
|
|
155
|
+
* Adapters must return plain arrays of records (already exported).
|
|
156
|
+
*/
|
|
157
|
+
export async function loadObsidianKnowledgePreview({
|
|
158
|
+
loadEngramExport = null,
|
|
159
|
+
loadGraphifyExport = null,
|
|
160
|
+
kairoRoot = "/virtual/Kairo",
|
|
161
|
+
generatedAt = new Date().toISOString(),
|
|
162
|
+
maxProposals = MAX_PROPOSALS
|
|
163
|
+
} = {}) {
|
|
164
|
+
const diagnostics = [];
|
|
165
|
+
let decisions = [];
|
|
166
|
+
let architecture = [];
|
|
167
|
+
|
|
168
|
+
if (typeof loadEngramExport === "function") {
|
|
169
|
+
try {
|
|
170
|
+
const raw = await loadEngramExport();
|
|
171
|
+
decisions = Array.isArray(raw) ? raw : Array.isArray(raw?.decisions) ? raw.decisions : [];
|
|
172
|
+
if (!Array.isArray(raw) && raw != null && !Array.isArray(raw?.decisions)) {
|
|
173
|
+
diagnostics.push("engram export shape unrecognized");
|
|
174
|
+
}
|
|
175
|
+
} catch (err) {
|
|
176
|
+
diagnostics.push(`engram export failed: ${String(err?.message ?? err)}`);
|
|
177
|
+
}
|
|
178
|
+
} else {
|
|
179
|
+
diagnostics.push("engram export adapter not provided");
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
if (typeof loadGraphifyExport === "function") {
|
|
183
|
+
try {
|
|
184
|
+
const raw = await loadGraphifyExport();
|
|
185
|
+
architecture = Array.isArray(raw)
|
|
186
|
+
? raw
|
|
187
|
+
: Array.isArray(raw?.architecture)
|
|
188
|
+
? raw.architecture
|
|
189
|
+
: Array.isArray(raw?.communities)
|
|
190
|
+
? raw.communities
|
|
191
|
+
: [];
|
|
192
|
+
if (
|
|
193
|
+
!Array.isArray(raw)
|
|
194
|
+
&& raw != null
|
|
195
|
+
&& !Array.isArray(raw?.architecture)
|
|
196
|
+
&& !Array.isArray(raw?.communities)
|
|
197
|
+
) {
|
|
198
|
+
diagnostics.push("graphify export shape unrecognized");
|
|
199
|
+
}
|
|
200
|
+
} catch (err) {
|
|
201
|
+
diagnostics.push(`graphify export failed: ${String(err?.message ?? err)}`);
|
|
202
|
+
}
|
|
203
|
+
} else {
|
|
204
|
+
diagnostics.push("graphify export adapter not provided");
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
const preview = buildObsidianKnowledgePreview({
|
|
208
|
+
decisions, architecture, generatedAt, maxProposals, kairoRoot
|
|
209
|
+
});
|
|
210
|
+
return {
|
|
211
|
+
...preview,
|
|
212
|
+
diagnostics: [...diagnostics, ...(preview.diagnostics ?? [])]
|
|
213
|
+
};
|
|
214
|
+
}
|