@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
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: sdd-tasks
|
|
3
|
+
description: "Break an accepted design into ordered, reviewable tasks."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# SDD Tasks
|
|
7
|
+
|
|
8
|
+
Use this skill after design to produce a task list that one reviewer can follow without reopening scope.
|
|
9
|
+
|
|
10
|
+
## References
|
|
11
|
+
- [Phase contract](references/contract.md)
|
|
12
|
+
## Source of truth
|
|
13
|
+
|
|
14
|
+
- `AGENTS.md`
|
|
15
|
+
- `docs/ai/harness.md`
|
|
16
|
+
- `docs/ai/spec-driven-development.md`
|
|
17
|
+
- `docs/ai/testing.md`
|
|
18
|
+
|
|
19
|
+
## Workflow
|
|
20
|
+
|
|
21
|
+
1. Split the design into independent, reviewable units.
|
|
22
|
+
2. Order tasks by dependency and risk.
|
|
23
|
+
3. Attach acceptance and validation to each task.
|
|
24
|
+
4. Keep persona and SDD contracts out of the task bodies.
|
|
25
|
+
|
|
26
|
+
## Output
|
|
27
|
+
|
|
28
|
+
```txt
|
|
29
|
+
Task order:
|
|
30
|
+
Dependencies:
|
|
31
|
+
Acceptance per task:
|
|
32
|
+
Validation per task:
|
|
33
|
+
Next skill:
|
|
34
|
+
```
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
# sdd-tasks contract
|
|
2
|
+
Activation: After design is stable enough to decompose. Hard rules: Independent reviewable units with acceptance+validation; no persona in bodies.
|
|
3
|
+
Gates: Ordered list ready → sdd-apply · design hole → return to sdd-design Steps: Split units → order by dependency/risk → attach acceptance/validation → stop.
|
|
4
|
+
Output: Task order | Dependencies | Acceptance per task | Validation per task | Next skill
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: sdd-verify
|
|
3
|
+
description: "Validate applied work against acceptance and pipeline gates."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# SDD Verify
|
|
7
|
+
|
|
8
|
+
Use this skill after implementation to prove the task is done with evidence, not claims.
|
|
9
|
+
|
|
10
|
+
## References
|
|
11
|
+
- [Phase contract](references/contract.md)
|
|
12
|
+
## Source of truth
|
|
13
|
+
|
|
14
|
+
- `AGENTS.md`
|
|
15
|
+
- `docs/ai/harness.md`
|
|
16
|
+
- `docs/ai/quality-gates.md`
|
|
17
|
+
- `docs/ai/testing.md`
|
|
18
|
+
|
|
19
|
+
## Workflow
|
|
20
|
+
|
|
21
|
+
1. Re-read acceptance criteria for the completed task.
|
|
22
|
+
2. Run the relevant lint, type, test, and smoke checks.
|
|
23
|
+
3. Record failures, skips, and residual risk honestly.
|
|
24
|
+
4. Hand off to archive only when the evidence is complete.
|
|
25
|
+
|
|
26
|
+
## Output
|
|
27
|
+
|
|
28
|
+
```txt
|
|
29
|
+
Acceptance checked:
|
|
30
|
+
Commands run:
|
|
31
|
+
Results:
|
|
32
|
+
Residual risk:
|
|
33
|
+
Next skill:
|
|
34
|
+
```
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
# sdd-verify contract
|
|
2
|
+
Activation: After apply, before archive — prove done with evidence. Hard rules: Evidence over claims; record skips/failures/residual risk honestly.
|
|
3
|
+
Gates: Evidence complete → sdd-archive · acceptance fails → return to sdd-apply Steps: Re-read acceptance → run lint/type/test/smoke → record results → handoff.
|
|
4
|
+
Output: Acceptance checked | Commands run | Results | Residual risk | Next skill
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kal-elsam/kairo-runtime",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
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",
|
|
@@ -19,13 +19,14 @@ import {
|
|
|
19
19
|
reduceCockpitUi,
|
|
20
20
|
routeCockpitKey
|
|
21
21
|
} from "../src/global/ink/cockpit-controller.js";
|
|
22
|
+
import { formatProposalLines } from "../src/global/ink/cockpit-proposals.js";
|
|
22
23
|
import { ORCHESTRATOR_VIEWS } from "../src/global/ink/orchestrator-state.js";
|
|
23
24
|
|
|
24
25
|
const root = join(dirname(fileURLToPath(import.meta.url)), "..");
|
|
25
26
|
const require = createRequire(import.meta.url);
|
|
26
27
|
const pkg = require(join(root, "package.json"));
|
|
27
28
|
|
|
28
|
-
assert.equal(pkg.version, "0.
|
|
29
|
+
assert.equal(pkg.version, "0.5.0");
|
|
29
30
|
assert.ok(pkg.dependencies["ansi-escapes"]);
|
|
30
31
|
|
|
31
32
|
assert.equal(resolveLayoutMode({ columns: 120, rows: 40 }), LAYOUT_MODES.WIDE);
|
|
@@ -68,7 +69,7 @@ const mission = buildHomeMissionModel({
|
|
|
68
69
|
},
|
|
69
70
|
layoutMode: LAYOUT_MODES.MINIMAL
|
|
70
71
|
});
|
|
71
|
-
assert.match(mission.title, /HOME — smoke/);
|
|
72
|
+
assert.match(mission.title, /HOME — smoke|CONTROL CENTER/i);
|
|
72
73
|
assert.match(mission.readiness.headline, /LIMITED|READY|NEEDS/i);
|
|
73
74
|
assert.equal(mission.next.targetAction, "launch");
|
|
74
75
|
assert.match(mission.recent.headline, /Codex · Failed/);
|
|
@@ -84,7 +85,7 @@ const asciiNav = buildNavModel({
|
|
|
84
85
|
assert.equal(asciiNav.items[1].marker, ">");
|
|
85
86
|
assert.equal(asciiNav.items[0].current, true);
|
|
86
87
|
assert.equal(asciiNav.items[1].selected, true);
|
|
87
|
-
assert.match(asciiNav.explanation, /
|
|
88
|
+
assert.match(asciiNav.explanation, /Detected agents|IDEs|models|Harness|modules/i);
|
|
88
89
|
|
|
89
90
|
function applyKey(state, keyAction) {
|
|
90
91
|
const routed = routeCockpitKey(state, keyAction);
|
|
@@ -97,21 +98,21 @@ function smokeNavigation(layoutMode) {
|
|
|
97
98
|
layoutMode,
|
|
98
99
|
region: layoutMode === LAYOUT_MODES.MINIMAL ? COCKPIT_REGIONS.CONTENT : COCKPIT_REGIONS.NAV
|
|
99
100
|
});
|
|
100
|
-
const diagnosticsIndex = COCKPIT_NAV.findIndex((item) => item.id === "
|
|
101
|
+
const diagnosticsIndex = COCKPIT_NAV.findIndex((item) => item.id === "changes");
|
|
101
102
|
while (state.navIndex < diagnosticsIndex) {
|
|
102
103
|
state = applyKey(state, { type: "arrow", direction: "down" });
|
|
103
104
|
}
|
|
104
105
|
state = applyKey(state, { type: "enter" });
|
|
105
|
-
assert.equal(state.view, ORCHESTRATOR_VIEWS.
|
|
106
|
+
assert.equal(state.view, ORCHESTRATOR_VIEWS.CHANGES);
|
|
106
107
|
assert.equal(state.region, COCKPIT_REGIONS.NAV);
|
|
107
108
|
|
|
108
109
|
state = applyKey(state, { type: "arrow", direction: "up" });
|
|
109
110
|
state = applyKey(state, { type: "enter" });
|
|
110
|
-
assert.equal(state.view, ORCHESTRATOR_VIEWS.
|
|
111
|
-
assert.equal(state.region, COCKPIT_REGIONS.
|
|
111
|
+
assert.equal(state.view, ORCHESTRATOR_VIEWS.MODULES);
|
|
112
|
+
assert.equal(state.region, COCKPIT_REGIONS.NAV);
|
|
112
113
|
|
|
113
114
|
const footer = buildFooterModel({
|
|
114
|
-
view: ORCHESTRATOR_VIEWS.
|
|
115
|
+
view: ORCHESTRATOR_VIEWS.CHANGES,
|
|
115
116
|
region: COCKPIT_REGIONS.NAV,
|
|
116
117
|
unicode: false
|
|
117
118
|
});
|
|
@@ -126,4 +127,24 @@ function smokeNavigation(layoutMode) {
|
|
|
126
127
|
smokeNavigation(LAYOUT_MODES.WIDE);
|
|
127
128
|
smokeNavigation(LAYOUT_MODES.COMPACT);
|
|
128
129
|
|
|
130
|
+
const proposalLines = formatProposalLines([
|
|
131
|
+
{
|
|
132
|
+
id: "setup-local",
|
|
133
|
+
severity: "high",
|
|
134
|
+
title: "Finish local setup",
|
|
135
|
+
destination: "changes",
|
|
136
|
+
evidence: [{ type: "status", source: "status.overall", ref: "missing" }]
|
|
137
|
+
}
|
|
138
|
+
], {
|
|
139
|
+
budgets: {
|
|
140
|
+
stableUsedTokens: 1,
|
|
141
|
+
stableBudgetTokens: 10,
|
|
142
|
+
requestUsedTokens: 0,
|
|
143
|
+
requestBudgetTokens: 10
|
|
144
|
+
}
|
|
145
|
+
});
|
|
146
|
+
assert.match(proposalLines.join("\n"), /\[HIGH\] Finish local setup → changes/);
|
|
147
|
+
assert.match(proposalLines.join("\n"), /Budget · stable 1\/10/);
|
|
148
|
+
assert.match(proposalLines.join("\n"), /evidence: status\.overall/);
|
|
149
|
+
|
|
129
150
|
console.log("cockpit smoke OK");
|
package/scripts/smoke-test.sh
CHANGED
|
@@ -78,6 +78,15 @@ echo
|
|
|
78
78
|
echo "== kairo status (after install) =="
|
|
79
79
|
npx --no-install kairo status
|
|
80
80
|
|
|
81
|
+
echo
|
|
82
|
+
echo "== kairo components configure/verify sdd-core =="
|
|
83
|
+
npx --no-install kairo components configure sdd-core --agents codex,cursor --persona off --dry-run --json \
|
|
84
|
+
| node -e 'let s="";process.stdin.on("data",d=>s+=d);process.stdin.on("end",()=>{const p=JSON.parse(s);if(p.writes!==false||p.applied!==false)process.exit(1)})'
|
|
85
|
+
npx --no-install kairo components configure sdd-core --agents codex,cursor --persona off --yes --json \
|
|
86
|
+
| node -e 'let s="";process.stdin.on("data",d=>s+=d);process.stdin.on("end",()=>{const p=JSON.parse(s);const fs=require("fs");const path=require("path");if(p.writes!==false||(p.summary?.create??0)!==0||!p.receipt?.id)process.exit(1);if(!fs.existsSync(path.join(process.env.HARNESS_HOME,".agents","skills","sdd-init","SKILL.md")))process.exit(1)})'
|
|
87
|
+
npx --no-install kairo components verify sdd-core --agents codex,cursor --json \
|
|
88
|
+
| node -e 'let s="";process.stdin.on("data",d=>s+=d);process.stdin.on("end",()=>{const p=JSON.parse(s);if(p.status!=="configured"||p.ok!==true)process.exit(1)})'
|
|
89
|
+
|
|
81
90
|
SNAPSHOT="$(ls -A "$FAKE_HOME/.harness/backups" | head -1)"
|
|
82
91
|
BACKUP_FILE="$(ls -A "$FAKE_HOME/.harness/backups/$SNAPSHOT" | head -1)"
|
|
83
92
|
|
package/src/cli.js
CHANGED
|
@@ -4,6 +4,11 @@ import { basename, dirname, resolve } from "node:path";
|
|
|
4
4
|
import { ADAPTERS } from "./harness-files.js";
|
|
5
5
|
import { GLOBAL_AGENT_IDS } from "./global/registry.js";
|
|
6
6
|
import { COMPONENT_IDS, DEFAULT_COMPONENT_IDS } from "./global/component-registry.js";
|
|
7
|
+
import {
|
|
8
|
+
runComponentsConfigure,
|
|
9
|
+
runComponentsRollback,
|
|
10
|
+
runComponentsVerify
|
|
11
|
+
} from "./global/component-integration-cli.js";
|
|
7
12
|
import {
|
|
8
13
|
printGlobalComponents,
|
|
9
14
|
printGlobalDetect,
|
|
@@ -237,6 +242,15 @@ async function dispatchComponentsCommand(options, invoke) {
|
|
|
237
242
|
case "import":
|
|
238
243
|
await runComponentsImport(options);
|
|
239
244
|
return;
|
|
245
|
+
case "configure":
|
|
246
|
+
await runComponentsConfigure({ ...options, packageRoot });
|
|
247
|
+
return;
|
|
248
|
+
case "verify":
|
|
249
|
+
await runComponentsVerify({ ...options, packageRoot });
|
|
250
|
+
return;
|
|
251
|
+
case "rollback":
|
|
252
|
+
await runComponentsRollback(options);
|
|
253
|
+
return;
|
|
240
254
|
default:
|
|
241
255
|
throw new Error(
|
|
242
256
|
`Unknown components action "${options.componentsAction}". Run "${invoke} help".`
|
|
@@ -321,6 +335,8 @@ export function parseArgs(argv) {
|
|
|
321
335
|
components: null,
|
|
322
336
|
componentsAction: null,
|
|
323
337
|
componentId: null,
|
|
338
|
+
receiptId: null,
|
|
339
|
+
persona: null,
|
|
324
340
|
label: null,
|
|
325
341
|
outPath: null,
|
|
326
342
|
bundlePath: null,
|
|
@@ -358,6 +374,7 @@ export function parseArgs(argv) {
|
|
|
358
374
|
agent: null,
|
|
359
375
|
task: null,
|
|
360
376
|
model: null,
|
|
377
|
+
intelligenceBackend: null,
|
|
361
378
|
permissions: null,
|
|
362
379
|
captureTranscript: false,
|
|
363
380
|
follow: false,
|
|
@@ -438,6 +455,10 @@ export function parseArgs(argv) {
|
|
|
438
455
|
} else if (arg === "--apply") options.apply = true;
|
|
439
456
|
else if (arg === "--to") options.snapshot = args[++index];
|
|
440
457
|
else if (arg.startsWith("--to=")) options.snapshot = arg.slice("--to=".length);
|
|
458
|
+
else if (arg === "--receipt") options.receiptId = args[++index];
|
|
459
|
+
else if (arg.startsWith("--receipt=")) options.receiptId = arg.slice("--receipt=".length);
|
|
460
|
+
else if (arg === "--persona") options.persona = parsePersona(args[++index]);
|
|
461
|
+
else if (arg.startsWith("--persona=")) options.persona = parsePersona(arg.slice("--persona=".length));
|
|
441
462
|
else if (arg === "--limit") options.limit = parsePositiveInt(args[++index], "limit");
|
|
442
463
|
else if (arg.startsWith("--limit=")) options.limit = parsePositiveInt(arg.slice("--limit=".length), "limit");
|
|
443
464
|
else if (arg === "--command") options.historyCommand = args[++index];
|
|
@@ -462,6 +483,8 @@ export function parseArgs(argv) {
|
|
|
462
483
|
else if (arg.startsWith("--agent=")) options.agent = arg.slice("--agent=".length);
|
|
463
484
|
else if (arg === "--model") options.model = args[++index];
|
|
464
485
|
else if (arg.startsWith("--model=")) options.model = arg.slice("--model=".length);
|
|
486
|
+
else if (arg === "--backend") options.intelligenceBackend = args[++index] ?? "";
|
|
487
|
+
else if (arg.startsWith("--backend=")) options.intelligenceBackend = arg.slice("--backend=".length);
|
|
465
488
|
else if (arg === "--permissions") options.permissions = parsePathList(args[++index]);
|
|
466
489
|
else if (arg.startsWith("--permissions=")) options.permissions = parsePathList(arg.slice("--permissions=".length));
|
|
467
490
|
else if (arg === "--capture-transcript") options.captureTranscript = true;
|
|
@@ -514,7 +537,18 @@ function parseComponentsAction(args, options) {
|
|
|
514
537
|
|
|
515
538
|
if (action === "validate") return;
|
|
516
539
|
|
|
517
|
-
|
|
540
|
+
if (action === "configure" || action === "verify" || action === "rollback") {
|
|
541
|
+
const componentId = args[0];
|
|
542
|
+
if (!componentId || componentId.startsWith("-")) {
|
|
543
|
+
throw new Error(
|
|
544
|
+
`Missing component id. Use: ${formatCliCommand(`components ${action} <component-id>`)}`
|
|
545
|
+
);
|
|
546
|
+
}
|
|
547
|
+
options.componentId = args.shift();
|
|
548
|
+
return;
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
throw new Error(`Unknown components action "${action}". Use validate, init, pack, import, configure, verify, or rollback.`);
|
|
518
552
|
}
|
|
519
553
|
|
|
520
554
|
function parsePolicyAction(args, options) {
|
|
@@ -685,6 +719,14 @@ function parseAdapters(value) {
|
|
|
685
719
|
return items;
|
|
686
720
|
}
|
|
687
721
|
|
|
722
|
+
function parsePersona(value) {
|
|
723
|
+
const persona = String(value ?? "").trim().toLowerCase();
|
|
724
|
+
if (persona !== "off" && persona !== "teaching") {
|
|
725
|
+
throw new Error('Invalid --persona. Use "off" or "teaching".');
|
|
726
|
+
}
|
|
727
|
+
return persona;
|
|
728
|
+
}
|
|
729
|
+
|
|
688
730
|
function printHelp() {
|
|
689
731
|
const cli = PREFERRED_CLI;
|
|
690
732
|
console.log(`${BRAND.displayName} (${PACKAGE_NAME})
|
|
@@ -707,7 +749,9 @@ Usage:
|
|
|
707
749
|
${cli} runs stop <runId> [--json]
|
|
708
750
|
${cli} orchestrator [--json] Read-only agent capability diagnostics
|
|
709
751
|
${cli} intelligence [status|models|context|route|ask] [--json]
|
|
710
|
-
${cli} intelligence
|
|
752
|
+
${cli} intelligence models --backend opencode-go|opencode-zen|opencode
|
|
753
|
+
${cli} intelligence route --backend opencode-go --model <id> [--cloud-consent]
|
|
754
|
+
${cli} intelligence ask --prompt "..." [--backend <id>] [--model <id>] [--cloud-consent] [--yes] [--paths a,b]
|
|
711
755
|
${cli} setup [--dry-run] [--yes] [--confirm] [--simple] [--no-preflight] [--agents <list|all>] [--components <list>]
|
|
712
756
|
${cli} status [--json]
|
|
713
757
|
${cli} sync [--dry-run] [--yes] [--confirm] [--json] [--no-preflight]
|
|
@@ -732,6 +776,10 @@ Usage:
|
|
|
732
776
|
${cli} report [--json] [--out <file>] [--limit <n>]
|
|
733
777
|
${cli} components
|
|
734
778
|
${cli} components validate|init|pack|import ...
|
|
779
|
+
${cli} components configure engram-memory [--agents <list>] [--dry-run|--yes] [--json]
|
|
780
|
+
${cli} components configure sdd-core [--agents <list>] [--persona off|teaching] [--dry-run|--yes] [--json]
|
|
781
|
+
${cli} components verify sdd-core [--agents <list>] [--json]
|
|
782
|
+
${cli} components rollback engram-memory|sdd-core --receipt <id> [--dry-run|--yes] [--json]
|
|
735
783
|
${cli} uninstall [--dry-run]
|
|
736
784
|
|
|
737
785
|
Scopes:
|
|
@@ -749,8 +797,9 @@ Commands:
|
|
|
749
797
|
runs List, inspect, or cancel agent runs under ~/.harness/runs/.
|
|
750
798
|
orchestrator Read-only capability registry diagnostics (--json supported).
|
|
751
799
|
intelligence Harness Engineering layer: backends, context packs, routing, budgets.
|
|
752
|
-
Local-first (Ollama). Cloud
|
|
753
|
-
|
|
800
|
+
Local-first (Ollama). Cloud only with --cloud-consent.
|
|
801
|
+
Ephemeral --backend/--model override preferredBackend/preferredModel.
|
|
802
|
+
Credentials via env only (OPENROUTER_API_KEY, OPENCODE_API_KEY, OLLAMA_HOST). Never stored.
|
|
754
803
|
setup Managed ecosystem setup. Interactive Ink UI (TTY). Use --simple for Clack prompts.
|
|
755
804
|
status Control panel: agents, components, drift, backups, next action.
|
|
756
805
|
sync Converge managed content (repair drift), then show status.
|
|
@@ -768,7 +817,7 @@ Commands:
|
|
|
768
817
|
rollback Preview or restore a prior config snapshot (--apply to write).
|
|
769
818
|
policy View or edit local operation preferences under ~/.harness/policy.json.
|
|
770
819
|
report Read-only diagnostics bundle: status, policy, adapters, diff, history.
|
|
771
|
-
components List, validate, scaffold, pack, or
|
|
820
|
+
components List, validate, scaffold, pack, import, or configure integrations (Engram, SDD).
|
|
772
821
|
uninstall Remove managed sections and global state. Backups are preserved.
|
|
773
822
|
init Alias for install --scope=workspace (legacy).
|
|
774
823
|
|
|
@@ -180,7 +180,7 @@ function summarizeDiagnostics(capabilities) {
|
|
|
180
180
|
};
|
|
181
181
|
}
|
|
182
182
|
|
|
183
|
-
function buildDiagnosticRecommendations({
|
|
183
|
+
export function buildDiagnosticRecommendations({
|
|
184
184
|
capabilities,
|
|
185
185
|
profile,
|
|
186
186
|
diagnostics,
|
|
@@ -206,13 +206,26 @@ function buildDiagnosticRecommendations({
|
|
|
206
206
|
}
|
|
207
207
|
}
|
|
208
208
|
|
|
209
|
-
if (!intelligenceSummary?.localAvailable && !intelligenceSummary?.
|
|
209
|
+
if (!intelligenceSummary?.localAvailable && !intelligenceSummary?.cloudConfigured) {
|
|
210
210
|
recommendations.push(
|
|
211
|
-
"No intelligence backend available. Start Ollama or set OPENROUTER_API_KEY (env only). Diagnostics mode remains available."
|
|
211
|
+
"No intelligence backend available. Start Ollama, install the OpenCode CLI, or set OPENCODE_API_KEY / OPENROUTER_API_KEY (env only). Diagnostics mode remains available."
|
|
212
|
+
);
|
|
213
|
+
} else if (
|
|
214
|
+
!intelligenceSummary?.localAvailable
|
|
215
|
+
&& intelligenceSummary?.cloudConfigured
|
|
216
|
+
&& !intelligenceSummary?.cloudAuthenticated
|
|
217
|
+
) {
|
|
218
|
+
recommendations.push(
|
|
219
|
+
`Cloud intelligence is configured but not authenticated. Run ${formatCliCommand("intelligence status")} to inspect evidence before invoke.`
|
|
212
220
|
);
|
|
213
221
|
} else if (!intelligenceSummary?.localAvailable && intelligenceSummary?.cloudAuthenticated) {
|
|
222
|
+
const provider = intelligenceSummary.opencodeGoAuthenticated
|
|
223
|
+
? "OpenCode Go"
|
|
224
|
+
: intelligenceSummary.opencodeZenAuthenticated
|
|
225
|
+
? "OpenCode Zen"
|
|
226
|
+
: "OpenRouter";
|
|
214
227
|
recommendations.push(
|
|
215
|
-
|
|
228
|
+
`${provider} is authenticated. Cloud invoke requires: ${formatCliCommand("intelligence ask --cloud-consent --yes")}.`
|
|
216
229
|
);
|
|
217
230
|
}
|
|
218
231
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { existsSync } from "node:fs";
|
|
2
2
|
import { mkdir, writeFile } from "node:fs/promises";
|
|
3
3
|
import { join, resolve } from "node:path";
|
|
4
|
+
import { COMPONENT_ID_PATTERN } from "./component-manifest.js";
|
|
4
5
|
import { COMPONENT_IDS } from "./component-registry.js";
|
|
5
6
|
import {
|
|
6
7
|
hasWorkspaceCatalog,
|
|
@@ -9,8 +10,6 @@ import {
|
|
|
9
10
|
workspaceCatalogPath
|
|
10
11
|
} from "./load-workspace-component-catalog.js";
|
|
11
12
|
import { formatCliCommand } from "./brand/cli.js";
|
|
12
|
-
|
|
13
|
-
const COMPONENT_ID_PATTERN = /^[a-z][a-z0-9-]*$/;
|
|
14
13
|
const DEFAULT_VERSION = "0.1.0";
|
|
15
14
|
const DEFAULT_ASSET = "README.md";
|
|
16
15
|
|
|
@@ -1,17 +1,34 @@
|
|
|
1
1
|
import { spawnSync } from "node:child_process";
|
|
2
2
|
import { existsSync } from "node:fs";
|
|
3
3
|
import { readFile } from "node:fs/promises";
|
|
4
|
-
import { join, resolve } from "node:path";
|
|
4
|
+
import { dirname, join, resolve } from "node:path";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
5
6
|
import { formatCliCommand } from "./brand/cli.js";
|
|
6
|
-
|
|
7
|
+
import { ensureIntegrationProvidersRegistered } from "./integrations/index.js";
|
|
8
|
+
import { requireIntegrationProvider } from "./integrations/provider-registry.js";
|
|
9
|
+
import { buildEngramIntegrationChecks, buildSddIntegrationChecks } from "./component-integration-cli.js";
|
|
10
|
+
import { KAIRO_ENGRAM_AGENT_IDS } from "./integrations/engram-evidence.js";
|
|
11
|
+
import { SDD_MANAGED_AGENT_IDS } from "./integrations/sdd-destinations.js";
|
|
12
|
+
import { harnessHomePaths } from "./paths.js";
|
|
13
|
+
import { readGlobalState } from "./state.js";
|
|
14
|
+
|
|
15
|
+
const PACKAGE_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), "../..");
|
|
7
16
|
const GRAPH_REPORT_COMMIT_PATTERN = /Built from commit:\s*`([0-9a-f]+)`/i;
|
|
8
17
|
|
|
9
|
-
export async function runComponentEcosystemChecks({
|
|
18
|
+
export async function runComponentEcosystemChecks({
|
|
19
|
+
installedComponents,
|
|
20
|
+
workspaceRoot = null,
|
|
21
|
+
homeDir = null
|
|
22
|
+
} = {}) {
|
|
10
23
|
const installedIds = new Set(installedComponents.map((component) => component.id));
|
|
11
24
|
const checks = [];
|
|
12
25
|
|
|
13
26
|
if (installedIds.has("engram-memory")) {
|
|
14
|
-
checks.push(
|
|
27
|
+
checks.push(...await buildEngramChecks({ homeDir }));
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (installedIds.has("sdd-core")) {
|
|
31
|
+
checks.push(...await buildSddChecks({ homeDir }));
|
|
15
32
|
}
|
|
16
33
|
|
|
17
34
|
if (installedIds.has("graphify-context")) {
|
|
@@ -21,14 +38,59 @@ export async function runComponentEcosystemChecks({ installedComponents, workspa
|
|
|
21
38
|
return checks;
|
|
22
39
|
}
|
|
23
40
|
|
|
24
|
-
function
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
41
|
+
async function buildEngramChecks({ homeDir }) {
|
|
42
|
+
ensureIntegrationProvidersRegistered();
|
|
43
|
+
try {
|
|
44
|
+
const provider = requireIntegrationProvider("engram");
|
|
45
|
+
const inspection = await provider.inspect({
|
|
46
|
+
homeDir: homeDir ?? undefined,
|
|
47
|
+
agentIds: KAIRO_ENGRAM_AGENT_IDS
|
|
48
|
+
});
|
|
49
|
+
return buildEngramIntegrationChecks(inspection);
|
|
50
|
+
} catch (error) {
|
|
51
|
+
return [{
|
|
52
|
+
name: "engram:binary",
|
|
53
|
+
status: "warning",
|
|
54
|
+
category: "integration",
|
|
55
|
+
componentId: "engram-memory",
|
|
56
|
+
detail: error instanceof Error ? error.message : "Engram inspection failed."
|
|
57
|
+
}];
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
async function buildSddChecks({ homeDir }) {
|
|
62
|
+
ensureIntegrationProvidersRegistered();
|
|
63
|
+
try {
|
|
64
|
+
const provider = requireIntegrationProvider("sdd-core");
|
|
65
|
+
const resolvedHome = homeDir ?? undefined;
|
|
66
|
+
const state = resolvedHome
|
|
67
|
+
? await readGlobalState(harnessHomePaths(resolvedHome).statePath)
|
|
68
|
+
: null;
|
|
69
|
+
const managed = new Set(SDD_MANAGED_AGENT_IDS);
|
|
70
|
+
const installedAgents = (state?.adapters ?? [])
|
|
71
|
+
.map((entry) => entry.id)
|
|
72
|
+
.filter((id) => managed.has(id));
|
|
73
|
+
const trackedFiles = Object.fromEntries(
|
|
74
|
+
(state?.sdd?.files ?? []).map((file) => [file.destinationPath, file.hash])
|
|
75
|
+
);
|
|
76
|
+
const verification = await provider.verify({
|
|
77
|
+
homeDir: resolvedHome,
|
|
78
|
+
requestedAgentIds: installedAgents.length ? installedAgents : [],
|
|
79
|
+
detectedAgentIds: installedAgents,
|
|
80
|
+
trackedFiles,
|
|
81
|
+
personaAgentIds: state?.sdd?.personaAgentIds ?? [],
|
|
82
|
+
packageRoot: PACKAGE_ROOT
|
|
83
|
+
});
|
|
84
|
+
return buildSddIntegrationChecks(verification);
|
|
85
|
+
} catch (error) {
|
|
86
|
+
return [{
|
|
87
|
+
name: "sdd-core:skills",
|
|
88
|
+
status: "warning",
|
|
89
|
+
category: "integration",
|
|
90
|
+
componentId: "sdd-core",
|
|
91
|
+
detail: error instanceof Error ? error.message : "SDD verification failed."
|
|
92
|
+
}];
|
|
93
|
+
}
|
|
32
94
|
}
|
|
33
95
|
|
|
34
96
|
async function buildGraphifyChecks(workspaceRoot) {
|
|
@@ -115,7 +177,6 @@ async function readGraphFreshness(workspaceRoot, reportPath) {
|
|
|
115
177
|
}
|
|
116
178
|
|
|
117
179
|
const isFresh = headCommit.startsWith(graphCommit) || graphCommit.startsWith(headCommit);
|
|
118
|
-
|
|
119
180
|
if (isFresh) {
|
|
120
181
|
return {
|
|
121
182
|
status: "ok",
|
|
@@ -134,9 +195,7 @@ function resolveGitHead(workspaceRoot) {
|
|
|
134
195
|
cwd: workspaceRoot,
|
|
135
196
|
encoding: "utf8"
|
|
136
197
|
});
|
|
137
|
-
|
|
138
198
|
if (result.status !== 0) return null;
|
|
139
|
-
|
|
140
199
|
const head = result.stdout.trim();
|
|
141
200
|
return head.length > 0 ? head : null;
|
|
142
201
|
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/** Public component health states derived from doctor checks. */
|
|
2
|
+
|
|
3
|
+
export const COMPONENT_HEALTH = Object.freeze({
|
|
4
|
+
HEALTHY: "healthy",
|
|
5
|
+
DEGRADED: "degraded",
|
|
6
|
+
DRIFTED: "drifted",
|
|
7
|
+
MISSING: "missing"
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
const CHECK_TO_HEALTH = Object.freeze({
|
|
11
|
+
missing: COMPONENT_HEALTH.MISSING,
|
|
12
|
+
stale: COMPONENT_HEALTH.DRIFTED,
|
|
13
|
+
warning: COMPONENT_HEALTH.DEGRADED,
|
|
14
|
+
ok: COMPONENT_HEALTH.HEALTHY
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
const HEALTH_RANK = Object.freeze({
|
|
18
|
+
[COMPONENT_HEALTH.MISSING]: 3,
|
|
19
|
+
[COMPONENT_HEALTH.DRIFTED]: 2,
|
|
20
|
+
[COMPONENT_HEALTH.DEGRADED]: 1,
|
|
21
|
+
[COMPONENT_HEALTH.HEALTHY]: 0
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
export function summarizeComponentHealth(checks = []) {
|
|
25
|
+
if (!Array.isArray(checks) || checks.length === 0) {
|
|
26
|
+
return COMPONENT_HEALTH.HEALTHY;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
let worst = COMPONENT_HEALTH.HEALTHY;
|
|
30
|
+
for (const check of checks) {
|
|
31
|
+
const mapped = CHECK_TO_HEALTH[check.status] ?? COMPONENT_HEALTH.DEGRADED;
|
|
32
|
+
if (HEALTH_RANK[mapped] > HEALTH_RANK[worst]) worst = mapped;
|
|
33
|
+
}
|
|
34
|
+
return worst;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function buildComponentHealthEntries(components, checks) {
|
|
38
|
+
return components.map((component) => {
|
|
39
|
+
const related = checks.filter((check) => check.componentId === component.id);
|
|
40
|
+
return {
|
|
41
|
+
id: component.id,
|
|
42
|
+
version: component.version,
|
|
43
|
+
source: component.source ?? "bundled",
|
|
44
|
+
status: summarizeComponentHealth(related),
|
|
45
|
+
checks: related.map((check) => ({
|
|
46
|
+
name: check.name,
|
|
47
|
+
status: check.status,
|
|
48
|
+
type: check.category ?? null
|
|
49
|
+
}))
|
|
50
|
+
};
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function doctorAffectedByComponentHealth(health) {
|
|
55
|
+
return health === COMPONENT_HEALTH.MISSING || health === COMPONENT_HEALTH.DRIFTED;
|
|
56
|
+
}
|