@kal-elsam/kairo-runtime 0.1.2 → 0.1.4

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.
@@ -20,6 +20,32 @@
20
20
  "claude": "- In repos with workspace harness: repo `AGENTS.md` wins over `CLAUDE.md` pointers.",
21
21
  "opencode": "- In repos with workspace harness: also read `.opencode/` SDD assets when present."
22
22
  }
23
+ },
24
+ {
25
+ "id": "engram-memory",
26
+ "label": "Engram Memory",
27
+ "version": "1.0.0",
28
+ "defaultEnabled": false,
29
+ "assetFiles": ["memory.md"],
30
+ "adapterHints": {
31
+ "cursor": "- Engram MCP (`mem_*` tools) is not auto-installed; configure in Cursor MCP settings when ready.",
32
+ "codex": "- Engram MCP is not auto-installed; add MCP server config when ready.",
33
+ "claude": "- Engram MCP is not auto-installed; add MCP server config when ready.",
34
+ "opencode": "- Engram MCP is not auto-installed; add MCP server config when ready."
35
+ }
36
+ },
37
+ {
38
+ "id": "graphify-context",
39
+ "label": "Graphify Context",
40
+ "version": "1.0.0",
41
+ "defaultEnabled": false,
42
+ "assetFiles": ["context-graph.md"],
43
+ "adapterHints": {
44
+ "cursor": "- Graphify CLI is not auto-installed; run `graphify update .` after code changes when graphify is in PATH.",
45
+ "codex": "- Graphify CLI is not auto-installed; run `graphify update .` after code changes when graphify is in PATH.",
46
+ "claude": "- Graphify CLI is not auto-installed; run `graphify update .` after code changes when graphify is in PATH.",
47
+ "opencode": "- Graphify CLI is not auto-installed; run `graphify update .` after code changes when graphify is in PATH."
48
+ }
23
49
  }
24
50
  ]
25
51
  }
@@ -0,0 +1,58 @@
1
+ # Engram Memory Contract
2
+
3
+ > Managed by `@kal-elsam/kairo-runtime` component `engram-memory`.
4
+
5
+ ## Role
6
+
7
+ Engram is an optional external memory layer. It indexes decisions, bugs, and
8
+ conventions across sessions. It does not replace the repository as source of
9
+ truth.
10
+
11
+ ## Authority order
12
+
13
+ ```txt
14
+ 1. Current user instruction
15
+ 2. Repository AGENTS.md (when working inside a repo)
16
+ 3. Repository docs/ai/ and docs/specs/
17
+ 4. Engram (persistent memory)
18
+ 5. Graphify (context graph)
19
+ ```
20
+
21
+ If Engram contradicts AGENTS.md or repo docs, the repo wins. Update or discard
22
+ stale memory instead of following it blindly.
23
+
24
+ ## When to search memory
25
+
26
+ Search Engram before:
27
+
28
+ - Starting a non-trivial task or a standard/complex spec.
29
+ - Diagnosing a bug that feels familiar or recurring.
30
+ - Proposing a new architecture or convention.
31
+ - Resuming work after context compaction or a new session.
32
+ - Repeating a loop task (see loop-retrospective guidance when present).
33
+
34
+ Skip memory search for trivial changes (typos, formatting, comments) or tasks
35
+ fully specified in an active spec.
36
+
37
+ ## When to save memory
38
+
39
+ Save proactively after:
40
+
41
+ - Architecture or policy decisions (with reasoning, not just the outcome).
42
+ - Bug root causes that may recur.
43
+ - Undocumented conventions discovered in code review.
44
+ - Loop retrospectives and discarded approaches (with why they were rejected).
45
+
46
+ Do not save secrets, credentials, PII, ephemeral debug output, or content
47
+ already documented in AGENTS.md or docs/ai/.
48
+
49
+ ## MCP tools
50
+
51
+ Engram is not bundled with Kairo Runtime. Configure Engram MCP tools
52
+ (`mem_search`, `mem_save`, etc.) in your agent when you want persistent memory.
53
+ Kairo only ships the contract and health checks — not the MCP server itself.
54
+
55
+ ## Decision traceability
56
+
57
+ Important decisions belong in `docs/ai/decision-log.md` or an ADR. Engram
58
+ indexes; the repo certifies.
@@ -0,0 +1,58 @@
1
+ # Graphify Context Contract
2
+
3
+ > Managed by `@kal-elsam/kairo-runtime` component `graphify-context`.
4
+
5
+ ## Role
6
+
7
+ Graphify is an optional context graph for architecture navigation. It maps
8
+ modules, dependencies, features, and risks. It does not replace reading code or
9
+ repo documentation.
10
+
11
+ ## Authority order
12
+
13
+ ```txt
14
+ 1. Current user instruction
15
+ 2. Repository AGENTS.md (when working inside a repo)
16
+ 3. Repository docs/ai/ and docs/specs/
17
+ 4. Engram (persistent memory)
18
+ 5. Graphify (context graph)
19
+ ```
20
+
21
+ If the graph contradicts code or repo docs, investigate the code first. Regenerate
22
+ the graph with `graphify update .` — never edit graph artifacts by hand.
23
+
24
+ ## When to consult Graphify
25
+
26
+ Before answering architecture or cross-module questions:
27
+
28
+ 1. Read `graphify-out/GRAPH_REPORT.md` for god nodes and community structure.
29
+ 2. If `graphify-out/wiki/index.md` exists, navigate it instead of scanning raw files.
30
+ 3. For "how does X relate to Y", prefer `graphify query`, `graphify path`, or
31
+ `graphify explain` over broad grep when the graph is fresh.
32
+
33
+ Before modifying code in an unfamiliar area:
34
+
35
+ - Check module boundaries and dependency fan-in in the graph.
36
+ - Note circular dependencies, god modules, and untested high-centrality nodes.
37
+
38
+ ## When to update the graph
39
+
40
+ Run `graphify update .` after modifying code files in a session (AST-only, no API
41
+ cost). Compare `GRAPH_REPORT.md` commit hash with `git rev-parse HEAD` to detect
42
+ staleness.
43
+
44
+ ## CLI prerequisite
45
+
46
+ Graphify is not bundled with Kairo Runtime. Install the `graphify` CLI separately
47
+ when you want local graph generation. Kairo only ships the contract and health
48
+ checks.
49
+
50
+ ## Graph signals to watch
51
+
52
+ | Signal | Risk | Action |
53
+ |---|---|---|
54
+ | Circular dependencies | Hidden coupling | Report and propose cycle break |
55
+ | High fan-in ("god module") | Risky change surface | Evaluate split or narrower API |
56
+ | Orphan module | Dead code or stale docs | Confirm before deleting |
57
+ | High centrality, no tests | Silent regression | Prioritize coverage before edits |
58
+ | Spec without code | Pending or abandoned work | Verify with human |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kal-elsam/kairo-runtime",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "description": "Kairo Runtime — local agent operating system for Codex, Cursor, Claude, Gemini, Copilot, Engram, and Graphify.",
5
5
  "type": "module",
6
6
  "homepage": "https://github.com/Kal-elSam/harness#readme",
@@ -184,7 +184,7 @@ require_cmd npm
184
184
  NODE_VERSION="$(node --version 2>/dev/null || true)"
185
185
  NPM_VERSION="$(npm --version 2>/dev/null || true)"
186
186
  GLOBAL_SPEC="$(resolve_global_spec)"
187
- GLOBAL_INSTALL_CMD="npm install -g ${GLOBAL_SPEC}"
187
+ GLOBAL_INSTALL_CMD="npm install -g --force ${GLOBAL_SPEC}"
188
188
  SETUP_CMD="${PREFERRED_CLI} setup ${SETUP_MODE}${SETUP_EXTRA:+ ${SETUP_EXTRA}}"
189
189
 
190
190
  printf '%s\n' \
@@ -224,7 +224,7 @@ fi
224
224
 
225
225
  printf '%s\n' "Installing global CLI..." ""
226
226
  # shellcheck disable=SC2086
227
- npm install -g ${GLOBAL_SPEC}
227
+ npm install -g --force ${GLOBAL_SPEC}
228
228
 
229
229
  KAIRO_BIN="$(resolve_kairo_bin || true)"
230
230
  if [ -z "$KAIRO_BIN" ]; then
@@ -2,6 +2,31 @@ import { fileURLToPath } from "node:url";
2
2
  import path from "node:path";
3
3
 
4
4
  const DEFAULT_REPO = "Kal-elSam/harness";
5
+ const LEGACY_HARNESS_MINOR_CUTOFF = 29;
6
+
7
+ function parseSemver(version) {
8
+ const match = /^(\d+)\.(\d+)\.(\d+)(?:-.+)?$/.exec(version);
9
+
10
+ if (!match) {
11
+ return null;
12
+ }
13
+
14
+ return {
15
+ major: Number(match[1]),
16
+ minor: Number(match[2]),
17
+ patch: Number(match[3])
18
+ };
19
+ }
20
+
21
+ export function usesLegacyHarnessTag(version) {
22
+ const semver = parseSemver(version);
23
+
24
+ if (!semver) {
25
+ return false;
26
+ }
27
+
28
+ return semver.major === 0 && semver.minor >= LEGACY_HARNESS_MINOR_CUTOFF;
29
+ }
5
30
 
6
31
  export function resolveInstallScriptRef({ version, tag = null }) {
7
32
  if (version === "latest") {
@@ -12,7 +37,11 @@ export function resolveInstallScriptRef({ version, tag = null }) {
12
37
  return tag;
13
38
  }
14
39
 
15
- return `v${version}`;
40
+ if (usesLegacyHarnessTag(version)) {
41
+ return `v${version}`;
42
+ }
43
+
44
+ return `kairo-runtime-v${version}`;
16
45
  }
17
46
 
18
47
  export function resolveInstallScriptUrl({
@@ -1,10 +1,14 @@
1
1
  import { join } from "node:path";
2
+ import { buildEngramMemoryManagedSection } from "./components/engram-memory.js";
3
+ import { buildGraphifyContextManagedSection } from "./components/graphify-context.js";
2
4
  import { buildOrchestratorManagedSection } from "./components/orchestrator.js";
3
5
  import { buildSddCoreManagedSection } from "./components/sdd-core.js";
4
6
 
5
7
  export const COMPONENT_BUILDERS = {
6
8
  orchestrator: buildOrchestratorManagedSection,
7
- "sdd-core": buildSddCoreManagedSection
9
+ "sdd-core": buildSddCoreManagedSection,
10
+ "engram-memory": buildEngramMemoryManagedSection,
11
+ "graphify-context": buildGraphifyContextManagedSection
8
12
  };
9
13
 
10
14
  export function resolveComponentBuilder(componentId) {
@@ -0,0 +1,147 @@
1
+ import { spawnSync } from "node:child_process";
2
+ import { existsSync } from "node:fs";
3
+ import { readFile } from "node:fs/promises";
4
+ import { join, resolve } from "node:path";
5
+ import { formatCliCommand } from "./brand/cli.js";
6
+
7
+ const GRAPH_REPORT_COMMIT_PATTERN = /Built from commit:\s*`([0-9a-f]+)`/i;
8
+
9
+ export async function runComponentEcosystemChecks({ installedComponents, workspaceRoot = null } = {}) {
10
+ const installedIds = new Set(installedComponents.map((component) => component.id));
11
+ const checks = [];
12
+
13
+ if (installedIds.has("engram-memory")) {
14
+ checks.push(buildEngramMcpCheck());
15
+ }
16
+
17
+ if (installedIds.has("graphify-context")) {
18
+ checks.push(...await buildGraphifyChecks(workspaceRoot));
19
+ }
20
+
21
+ return checks;
22
+ }
23
+
24
+ function buildEngramMcpCheck() {
25
+ return {
26
+ name: "engram:mcp-tools",
27
+ status: "warning",
28
+ category: "integration",
29
+ componentId: "engram-memory",
30
+ detail: "Engram MCP is not bundled. Configure mem_* tools in your agent (user-engram or plugin-engram-engram) when ready."
31
+ };
32
+ }
33
+
34
+ async function buildGraphifyChecks(workspaceRoot) {
35
+ const checks = [];
36
+
37
+ if (!workspaceRoot) {
38
+ checks.push({
39
+ name: "graphify:workspace",
40
+ status: "warning",
41
+ category: "integration",
42
+ componentId: "graphify-context",
43
+ detail: "No workspace root; graph freshness checks skipped. Run doctor from a repo cwd."
44
+ });
45
+ return checks;
46
+ }
47
+
48
+ const resolvedRoot = resolve(workspaceRoot);
49
+ const graphifyAvailable = isCommandAvailable("graphify");
50
+
51
+ checks.push({
52
+ name: "graphify:cli",
53
+ status: graphifyAvailable ? "ok" : "warning",
54
+ category: "integration",
55
+ componentId: "graphify-context",
56
+ detail: graphifyAvailable
57
+ ? "graphify CLI found in PATH."
58
+ : "graphify CLI not found in PATH. Install separately when you want local graph generation."
59
+ });
60
+
61
+ const graphPath = join(resolvedRoot, "graphify-out", "graph.json");
62
+ const reportPath = join(resolvedRoot, "graphify-out", "GRAPH_REPORT.md");
63
+
64
+ if (!existsSync(graphPath)) {
65
+ checks.push({
66
+ name: "graphify:graph.json",
67
+ status: "warning",
68
+ category: "integration",
69
+ componentId: "graphify-context",
70
+ detail: existsSync(join(resolvedRoot, "graphify-out"))
71
+ ? "graphify-out/graph.json is absent. Run `graphify update .` when the workspace is ready."
72
+ : "graphify-out/ not found. Run `graphify update .` when the workspace is ready."
73
+ });
74
+ return checks;
75
+ }
76
+
77
+ const freshness = await readGraphFreshness(resolvedRoot, reportPath);
78
+ checks.push({
79
+ name: "graphify:graph.json",
80
+ status: freshness.status,
81
+ category: "integration",
82
+ componentId: "graphify-context",
83
+ detail: freshness.detail
84
+ });
85
+
86
+ return checks;
87
+ }
88
+
89
+ async function readGraphFreshness(workspaceRoot, reportPath) {
90
+ if (!existsSync(reportPath)) {
91
+ return {
92
+ status: "warning",
93
+ detail: "graphify-out/graph.json exists but GRAPH_REPORT.md is missing. Run `graphify update .` to refresh metadata."
94
+ };
95
+ }
96
+
97
+ const report = await readFile(reportPath, "utf8");
98
+ const match = report.match(GRAPH_REPORT_COMMIT_PATTERN);
99
+
100
+ if (!match) {
101
+ return {
102
+ status: "ok",
103
+ detail: "graphify-out/graph.json present. Commit metadata unavailable in GRAPH_REPORT.md."
104
+ };
105
+ }
106
+
107
+ const graphCommit = match[1];
108
+ const headCommit = resolveGitHead(workspaceRoot);
109
+
110
+ if (!headCommit) {
111
+ return {
112
+ status: "warning",
113
+ detail: `graphify-out/graph.json present (built from ${graphCommit}). Git HEAD unavailable for staleness check.`
114
+ };
115
+ }
116
+
117
+ const isFresh = headCommit.startsWith(graphCommit) || graphCommit.startsWith(headCommit);
118
+
119
+ if (isFresh) {
120
+ return {
121
+ status: "ok",
122
+ detail: `graphify-out/graph.json in sync with HEAD (${headCommit.slice(0, 8)}).`
123
+ };
124
+ }
125
+
126
+ return {
127
+ status: "warning",
128
+ detail: `graphify-out/graph.json may be stale (graph ${graphCommit}, HEAD ${headCommit.slice(0, 8)}). Run \`graphify update .\` or ${formatCliCommand("sync")} for managed assets only.`
129
+ };
130
+ }
131
+
132
+ function resolveGitHead(workspaceRoot) {
133
+ const result = spawnSync("git", ["rev-parse", "HEAD"], {
134
+ cwd: workspaceRoot,
135
+ encoding: "utf8"
136
+ });
137
+
138
+ if (result.status !== 0) return null;
139
+
140
+ const head = result.stdout.trim();
141
+ return head.length > 0 ? head : null;
142
+ }
143
+
144
+ function isCommandAvailable(command) {
145
+ const result = spawnSync("which", [command], { encoding: "utf8" });
146
+ return result.status === 0 && result.stdout.trim().length > 0;
147
+ }
@@ -0,0 +1,16 @@
1
+ import { join } from "node:path";
2
+
3
+ export function buildEngramMemoryManagedSection(context, adapter, catalogEntry) {
4
+ const contractPath = join(context.componentsDir, "engram-memory", "memory.md");
5
+ const adapterNote = catalogEntry?.adapterHints?.[adapter.id] ?? null;
6
+
7
+ return [
8
+ "### Engram Memory",
9
+ "",
10
+ `- Contract: ${contractPath}`,
11
+ "- Optional persistent memory; repo AGENTS.md and docs/ai/ govern when present.",
12
+ "- Search memory before non-trivial tasks; save decisions, bugs, and conventions proactively.",
13
+ "- Authority: user > AGENTS.md > repo docs > Engram > Graphify.",
14
+ adapterNote
15
+ ].filter(Boolean).join("\n");
16
+ }
@@ -0,0 +1,16 @@
1
+ import { join } from "node:path";
2
+
3
+ export function buildGraphifyContextManagedSection(context, adapter, catalogEntry) {
4
+ const contractPath = join(context.componentsDir, "graphify-context", "context-graph.md");
5
+ const adapterNote = catalogEntry?.adapterHints?.[adapter.id] ?? null;
6
+
7
+ return [
8
+ "### Graphify Context",
9
+ "",
10
+ `- Contract: ${contractPath}`,
11
+ "- Optional architecture graph; read GRAPH_REPORT.md before cross-module questions.",
12
+ "- Run `graphify update .` after code changes when the CLI is available.",
13
+ "- Authority: user > AGENTS.md > repo docs > Engram > Graphify.",
14
+ adapterNote
15
+ ].filter(Boolean).join("\n");
16
+ }
@@ -1,5 +1,6 @@
1
1
  import { listBackupSnapshots } from "./backups.js";
2
2
  import { buildAdapterContext } from "./adapter-context.js";
3
+ import { runComponentEcosystemChecks } from "./component-ecosystem-checks.js";
3
4
  import { detectGlobalDrift, hasRepairableDrift } from "./drift.js";
4
5
  import { harnessHomePaths } from "./paths.js";
5
6
  import { resolveComponent } from "./component-registry.js";
@@ -23,6 +24,10 @@ export async function runGlobalDoctorChecks(homeDir, { packageRoot, workspaceRoo
23
24
 
24
25
  if (packageRoot) {
25
26
  checks.push(await backupsCheck(paths));
27
+ checks.push(...await runComponentEcosystemChecks({
28
+ installedComponents,
29
+ workspaceRoot
30
+ }));
26
31
  }
27
32
 
28
33
  const hasMissing = checks.some((check) => check.status === "missing");