@duypham93/openkit 0.2.7 → 0.2.9
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/.opencode/install-manifest.json +1 -1
- package/.opencode/tests/session-start-hook.test.js +5 -13
- package/.opencode/tests/workflow-behavior.test.js +1 -1
- package/.opencode/tests/workflow-contract-consistency.test.js +1 -1
- package/.opencode/tests/workflow-state-cli.test.js +3 -3
- package/assets/openkit-install.json.template +1 -1
- package/docs/maintainer/README.md +1 -1
- package/docs/operations/runbooks/openkit-daily-usage.md +2 -2
- package/hooks/session-start +3 -157
- package/hooks/session-start.js +210 -0
- package/package.json +1 -1
- package/registry.json +1 -1
- package/src/command-detection.js +37 -0
- package/src/global/doctor.js +4 -5
- package/src/global/install-state.js +3 -1
- package/src/global/materialize.js +3 -2
- package/src/global/workspace-shim.js +68 -13
- package/src/global/workspace-state.js +116 -1
- package/src/install/install-state.js +3 -1
- package/src/install/materialize.js +2 -1
- package/src/runtime/doctor.js +2 -9
- package/src/version.js +25 -0
- package/tests/cli/openkit-cli.test.js +92 -14
- package/tests/global/doctor.test.js +8 -1
- package/tests/global/ensure-install.test.js +2 -1
- package/tests/runtime/doctor.test.js +5 -5
|
@@ -58,7 +58,7 @@ function makeQuickState(overrides = {}) {
|
|
|
58
58
|
}
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
-
function writeManifest(projectRoot) {
|
|
61
|
+
function writeManifest(projectRoot, version = "0.2.9") {
|
|
62
62
|
const opencodeDir = path.join(projectRoot, ".opencode")
|
|
63
63
|
fs.mkdirSync(opencodeDir, { recursive: true })
|
|
64
64
|
fs.writeFileSync(
|
|
@@ -66,7 +66,7 @@ function writeManifest(projectRoot) {
|
|
|
66
66
|
`${JSON.stringify({
|
|
67
67
|
kit: {
|
|
68
68
|
name: "OpenKit AI Software Factory",
|
|
69
|
-
version
|
|
69
|
+
version,
|
|
70
70
|
entryAgent: "MasterOrchestrator",
|
|
71
71
|
},
|
|
72
72
|
}, null, 2)}\n`,
|
|
@@ -130,14 +130,6 @@ function writeMetaSkill(projectRoot) {
|
|
|
130
130
|
fs.writeFileSync(path.join(skillDir, "SKILL.md"), "# using-skills\n", "utf8")
|
|
131
131
|
}
|
|
132
132
|
|
|
133
|
-
function writeFailingPythonShim(projectRoot) {
|
|
134
|
-
const binDir = path.join(projectRoot, "bin")
|
|
135
|
-
const shimPath = path.join(binDir, "python3-shim")
|
|
136
|
-
fs.mkdirSync(binDir, { recursive: true })
|
|
137
|
-
fs.writeFileSync(shimPath, "#!/usr/bin/env bash\nexit 1\n", { encoding: "utf8", mode: 0o755 })
|
|
138
|
-
return shimPath
|
|
139
|
-
}
|
|
140
|
-
|
|
141
133
|
test("session-start emits mode-aware resume hint for quick tasks", () => {
|
|
142
134
|
const projectRoot = makeTempProject()
|
|
143
135
|
|
|
@@ -157,7 +149,7 @@ test("session-start emits mode-aware resume hint for quick tasks", () => {
|
|
|
157
149
|
|
|
158
150
|
assert.equal(result.status, 0)
|
|
159
151
|
assert.match(result.stdout, /<openkit_runtime_status>/)
|
|
160
|
-
assert.match(result.stdout, /kit: OpenKit AI Software Factory v0\.
|
|
152
|
+
assert.match(result.stdout, /kit: OpenKit AI Software Factory v0\.2\.9/)
|
|
161
153
|
assert.match(result.stdout, /startup skill: skipped/)
|
|
162
154
|
assert.match(result.stdout, /node \.opencode\/workflow-state\.js status/)
|
|
163
155
|
assert.match(result.stdout, /node \.opencode\/workflow-state\.js doctor/)
|
|
@@ -265,9 +257,10 @@ test("session-start prints canonical resume guidance and inspection commands", (
|
|
|
265
257
|
test("session-start degrades gracefully when the JSON helper fails", () => {
|
|
266
258
|
const projectRoot = makeTempProject()
|
|
267
259
|
|
|
260
|
+
const manifestPath = path.join(projectRoot, ".opencode", "opencode.json")
|
|
268
261
|
writeManifest(projectRoot)
|
|
269
262
|
writeState(projectRoot, makeQuickState())
|
|
270
|
-
|
|
263
|
+
fs.writeFileSync(manifestPath, '{"kit":', 'utf8')
|
|
271
264
|
|
|
272
265
|
const result = spawnSync(path.resolve(__dirname, "../../hooks/session-start"), {
|
|
273
266
|
cwd: projectRoot,
|
|
@@ -277,7 +270,6 @@ test("session-start degrades gracefully when the JSON helper fails", () => {
|
|
|
277
270
|
OPENKIT_PROJECT_ROOT: projectRoot,
|
|
278
271
|
OPENKIT_SESSION_START_NO_SKILL: "1",
|
|
279
272
|
OPENKIT_WORKFLOW_STATE: path.join(projectRoot, ".opencode", "workflow-state.json"),
|
|
280
|
-
OPENKIT_PYTHON_BIN: pythonShim,
|
|
281
273
|
},
|
|
282
274
|
})
|
|
283
275
|
|
|
@@ -30,7 +30,7 @@ function setupTempRuntime(projectRoot) {
|
|
|
30
30
|
`${JSON.stringify({
|
|
31
31
|
kit: {
|
|
32
32
|
name: "OpenKit AI Software Factory",
|
|
33
|
-
version: "0.
|
|
33
|
+
version: "0.2.9",
|
|
34
34
|
entryAgent: "MasterOrchestrator",
|
|
35
35
|
registry: {
|
|
36
36
|
path: "registry.json",
|
|
@@ -239,7 +239,7 @@ test("status command prints workflow and runtime summary", () => {
|
|
|
239
239
|
|
|
240
240
|
assert.equal(result.status, 0)
|
|
241
241
|
assert.match(result.stdout, /OpenKit runtime status:/)
|
|
242
|
-
assert.match(result.stdout, /kit: OpenKit AI Software Factory v0\.
|
|
242
|
+
assert.match(result.stdout, /kit: OpenKit AI Software Factory v0\.2\.9/)
|
|
243
243
|
assert.match(result.stdout, /entry agent: MasterOrchestrator/)
|
|
244
244
|
assert.match(result.stdout, /active profile: openkit-core/)
|
|
245
245
|
assert.match(result.stdout, /registry: .*registry\.json/)
|
|
@@ -577,7 +577,7 @@ test("version command prints kit metadata version", () => {
|
|
|
577
577
|
})
|
|
578
578
|
|
|
579
579
|
assert.equal(result.status, 0)
|
|
580
|
-
assert.match(result.stdout, /OpenKit version: 0\.
|
|
580
|
+
assert.match(result.stdout, /OpenKit version: 0\.2\.9/)
|
|
581
581
|
assert.match(result.stdout, /active profile: openkit-core/)
|
|
582
582
|
})
|
|
583
583
|
|
|
@@ -40,7 +40,7 @@ Use it to find canonical repository docs and upkeep surfaces quickly. Do not tre
|
|
|
40
40
|
|
|
41
41
|
- The preferred end-user onboarding path is `npm install -g @duypham93/openkit` followed by `openkit run`.
|
|
42
42
|
- The first `openkit run` materializes the managed kit into the OpenCode home directory automatically.
|
|
43
|
-
- `openkit doctor` is
|
|
43
|
+
- `openkit doctor` is a non-mutating check for the global install and current workspace readiness state.
|
|
44
44
|
- `openkit install-global`, `openkit install`, and `openkit init` remain available as manual or compatibility commands.
|
|
45
45
|
- The package intentionally avoids npm `postinstall` side effects; setup happens inside the OpenKit CLI where failures and recovery steps are easier to explain.
|
|
46
46
|
|
|
@@ -80,7 +80,7 @@ If the boundary still feels fuzzy, use the `Lane Decision Matrix` in `context/co
|
|
|
80
80
|
|
|
81
81
|
### 1. Check global install and workspace health
|
|
82
82
|
|
|
83
|
-
Start with
|
|
83
|
+
Start with non-mutating checks:
|
|
84
84
|
|
|
85
85
|
```bash
|
|
86
86
|
openkit doctor
|
|
@@ -88,7 +88,7 @@ openkit doctor
|
|
|
88
88
|
|
|
89
89
|
What to look for:
|
|
90
90
|
|
|
91
|
-
- `doctor` confirms the global kit is installed, the workspace root
|
|
91
|
+
- `doctor` confirms the global kit is installed, shows the derived workspace root, and reports whether the current project can launch with OpenKit cleanly without mutating local workspace files
|
|
92
92
|
|
|
93
93
|
If `doctor` reports `install-missing`, run `openkit run` for first-time setup. If `doctor` reports other errors, fix those before trusting resume or task-board behavior.
|
|
94
94
|
|
package/hooks/session-start
CHANGED
|
@@ -1,162 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
# It teaches the agent how to use the Skills library by injecting the meta-skill.
|
|
5
|
-
|
|
6
|
-
set -e
|
|
3
|
+
set -euo pipefail
|
|
7
4
|
|
|
8
5
|
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
|
9
|
-
|
|
10
|
-
PROJECT_ROOT="${OPENKIT_PROJECT_ROOT:-$(pwd)}"
|
|
11
|
-
META_SKILL_PATH="$KIT_ROOT/skills/using-skills/SKILL.md"
|
|
12
|
-
STATE_PATH="${OPENKIT_WORKFLOW_STATE:-$PROJECT_ROOT/.opencode/workflow-state.json}"
|
|
13
|
-
MANIFEST_PATH="$KIT_ROOT/.opencode/opencode.json"
|
|
14
|
-
PYTHON_BIN="${OPENKIT_PYTHON_BIN:-python3}"
|
|
15
|
-
RUNTIME_SUMMARY_MODULE="$KIT_ROOT/.opencode/lib/runtime-summary.js"
|
|
16
|
-
|
|
17
|
-
KIT_NAME="OpenKit AI Software Factory"
|
|
18
|
-
KIT_VERSION="unknown"
|
|
19
|
-
ENTRY_AGENT="unknown"
|
|
20
|
-
JSON_HELPER_STATUS="ok"
|
|
21
|
-
|
|
22
|
-
if [ -f "$MANIFEST_PATH" ] && command -v "$PYTHON_BIN" >/dev/null 2>&1; then
|
|
23
|
-
KIT_INFO_RAW=$("$PYTHON_BIN" - "$MANIFEST_PATH" <<'PY' || true
|
|
24
|
-
import json
|
|
25
|
-
import sys
|
|
26
|
-
|
|
27
|
-
manifest_path = sys.argv[1]
|
|
28
|
-
|
|
29
|
-
try:
|
|
30
|
-
with open(manifest_path, "r", encoding="utf-8") as fh:
|
|
31
|
-
manifest = json.load(fh)
|
|
32
|
-
except Exception:
|
|
33
|
-
sys.exit(0)
|
|
34
|
-
|
|
35
|
-
kit = manifest.get("kit") or {}
|
|
36
|
-
print(kit.get("name", "OpenKit AI Software Factory"))
|
|
37
|
-
print(kit.get("version", "unknown"))
|
|
38
|
-
print(kit.get("entryAgent", "unknown"))
|
|
39
|
-
PY
|
|
40
|
-
)
|
|
41
|
-
KIT_INFO_1=$(printf '%s\n' "$KIT_INFO_RAW" | sed -n '1p')
|
|
42
|
-
KIT_INFO_2=$(printf '%s\n' "$KIT_INFO_RAW" | sed -n '2p')
|
|
43
|
-
KIT_INFO_3=$(printf '%s\n' "$KIT_INFO_RAW" | sed -n '3p')
|
|
44
|
-
if [ -n "$KIT_INFO_1" ] && [ -n "$KIT_INFO_2" ] && [ -n "$KIT_INFO_3" ]; then
|
|
45
|
-
KIT_NAME="$KIT_INFO_1"
|
|
46
|
-
KIT_VERSION="$KIT_INFO_2"
|
|
47
|
-
ENTRY_AGENT="$KIT_INFO_3"
|
|
48
|
-
else
|
|
49
|
-
JSON_HELPER_STATUS="degraded"
|
|
50
|
-
fi
|
|
51
|
-
elif [ -f "$MANIFEST_PATH" ]; then
|
|
52
|
-
JSON_HELPER_STATUS="degraded"
|
|
53
|
-
fi
|
|
54
|
-
|
|
55
|
-
if [ -n "${OPENKIT_SESSION_START_NO_SKILL:-}" ]; then
|
|
56
|
-
SKILL_STATUS="skipped"
|
|
57
|
-
elif [ -f "$META_SKILL_PATH" ]; then
|
|
58
|
-
SKILL_STATUS="loaded"
|
|
59
|
-
else
|
|
60
|
-
SKILL_STATUS="missing"
|
|
61
|
-
fi
|
|
62
|
-
|
|
63
|
-
echo "<openkit_runtime_status>"
|
|
64
|
-
echo "kit: $KIT_NAME v$KIT_VERSION"
|
|
65
|
-
echo "entry agent: $ENTRY_AGENT"
|
|
66
|
-
echo "state file: $STATE_PATH"
|
|
67
|
-
echo "startup skill: $SKILL_STATUS"
|
|
68
|
-
echo "json helper: $JSON_HELPER_STATUS"
|
|
69
|
-
echo "help: node .opencode/workflow-state.js status"
|
|
70
|
-
echo "doctor: node .opencode/workflow-state.js doctor"
|
|
71
|
-
echo "show: node .opencode/workflow-state.js show"
|
|
72
|
-
echo "</openkit_runtime_status>"
|
|
73
|
-
|
|
74
|
-
if [ -z "${OPENKIT_SESSION_START_NO_SKILL:-}" ] && [ -f "$META_SKILL_PATH" ]; then
|
|
75
|
-
echo "<skill_system_instruction>"
|
|
76
|
-
echo "You are running within the Open Kit AI Software Factory framework."
|
|
77
|
-
echo "Below are the rules for how you must discover and invoke your skills."
|
|
78
|
-
echo ""
|
|
79
|
-
cat "$META_SKILL_PATH"
|
|
80
|
-
echo "</skill_system_instruction>"
|
|
81
|
-
fi
|
|
82
|
-
|
|
83
|
-
if [ -f "$STATE_PATH" ] && [ "$JSON_HELPER_STATUS" = "ok" ] && command -v "$PYTHON_BIN" >/dev/null 2>&1; then
|
|
84
|
-
"$PYTHON_BIN" - "$STATE_PATH" "$RUNTIME_SUMMARY_MODULE" <<'PY' || true
|
|
85
|
-
import json
|
|
86
|
-
import os
|
|
87
|
-
import pathlib
|
|
88
|
-
import subprocess
|
|
89
|
-
import sys
|
|
90
|
-
|
|
91
|
-
state_path = sys.argv[1]
|
|
92
|
-
runtime_summary_module = sys.argv[2]
|
|
93
|
-
runtime_root = str(pathlib.Path(state_path).resolve().parents[1])
|
|
94
|
-
|
|
95
|
-
try:
|
|
96
|
-
with open(state_path, "r", encoding="utf-8") as fh:
|
|
97
|
-
state = json.load(fh)
|
|
98
|
-
except Exception:
|
|
99
|
-
sys.exit(0)
|
|
100
|
-
|
|
101
|
-
mode = state.get("mode")
|
|
102
|
-
stage = state.get("current_stage")
|
|
103
|
-
status = state.get("status")
|
|
104
|
-
owner = state.get("current_owner")
|
|
105
|
-
feature_id = state.get("feature_id")
|
|
106
|
-
feature_slug = state.get("feature_slug")
|
|
107
|
-
work_item_id = state.get("work_item_id")
|
|
108
|
-
|
|
109
|
-
if not all([mode, stage, status, owner]):
|
|
110
|
-
sys.exit(0)
|
|
111
|
-
|
|
112
|
-
task_summary = None
|
|
113
|
-
active_tasks = []
|
|
114
|
-
|
|
115
|
-
if mode == "full" and work_item_id:
|
|
116
|
-
try:
|
|
117
|
-
node_cmd = [
|
|
118
|
-
"node",
|
|
119
|
-
"-e",
|
|
120
|
-
(
|
|
121
|
-
"const { getRuntimeContext } = require(process.argv[1]);"
|
|
122
|
-
"const context = getRuntimeContext(process.argv[2], JSON.parse(process.argv[3]));"
|
|
123
|
-
"process.stdout.write(JSON.stringify(context));"
|
|
124
|
-
),
|
|
125
|
-
runtime_summary_module,
|
|
126
|
-
runtime_root,
|
|
127
|
-
json.dumps(state),
|
|
128
|
-
]
|
|
129
|
-
result = subprocess.run(node_cmd, check=True, capture_output=True, text=True)
|
|
130
|
-
runtime_context = json.loads(result.stdout.strip() or "{}")
|
|
131
|
-
board_summary = runtime_context.get("taskBoardSummary")
|
|
132
|
-
if board_summary:
|
|
133
|
-
task_summary = (
|
|
134
|
-
f"task board: {board_summary.get('total', 0)} tasks | "
|
|
135
|
-
f"ready {board_summary.get('ready', 0)} | active {board_summary.get('active', 0)}"
|
|
136
|
-
)
|
|
137
|
-
active_tasks = board_summary.get("activeTasks") or []
|
|
138
|
-
except Exception:
|
|
139
|
-
task_summary = None
|
|
140
|
-
active_tasks = []
|
|
6
|
+
NODE_BIN="${OPENKIT_NODE_BIN:-node}"
|
|
141
7
|
|
|
142
|
-
|
|
143
|
-
print("OpenKit workflow resume context detected.")
|
|
144
|
-
print(f"mode: {mode}")
|
|
145
|
-
print(f"stage: {stage}")
|
|
146
|
-
print(f"status: {status}")
|
|
147
|
-
print(f"owner: {owner}")
|
|
148
|
-
if feature_id and feature_slug:
|
|
149
|
-
print(f"work item: {feature_id} ({feature_slug})")
|
|
150
|
-
if work_item_id:
|
|
151
|
-
print(f"active work item id: {work_item_id}")
|
|
152
|
-
if task_summary:
|
|
153
|
-
print(task_summary)
|
|
154
|
-
if active_tasks:
|
|
155
|
-
print(f"active tasks: {'; '.join(active_tasks)}")
|
|
156
|
-
print("Read first: AGENTS.md -> context/navigation.md -> context/core/workflow.md -> .opencode/workflow-state.json")
|
|
157
|
-
print("Then load resume guidance from context/core/session-resume.md.")
|
|
158
|
-
if mode == "full" and active_tasks:
|
|
159
|
-
print("Parallel task support is not yet assumed safe by this hook; confirm with `node .opencode/workflow-state.js doctor` before relying on it.")
|
|
160
|
-
print("</workflow_resume_hint>")
|
|
161
|
-
PY
|
|
162
|
-
fi
|
|
8
|
+
exec "$NODE_BIN" "$DIR/session-start.js" "$@"
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import fs from 'node:fs';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { createRequire } from 'node:module';
|
|
6
|
+
import { fileURLToPath } from 'node:url';
|
|
7
|
+
|
|
8
|
+
const require = createRequire(import.meta.url);
|
|
9
|
+
const SCRIPT_DIR = path.dirname(fileURLToPath(import.meta.url));
|
|
10
|
+
const DEFAULT_KIT_ROOT = path.resolve(SCRIPT_DIR, '..');
|
|
11
|
+
|
|
12
|
+
function resolveKitRoot(projectRoot, statePath) {
|
|
13
|
+
if (process.env.OPENKIT_KIT_ROOT) {
|
|
14
|
+
return path.resolve(process.env.OPENKIT_KIT_ROOT);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const candidates = [
|
|
18
|
+
projectRoot,
|
|
19
|
+
path.dirname(path.resolve(statePath)),
|
|
20
|
+
DEFAULT_KIT_ROOT,
|
|
21
|
+
];
|
|
22
|
+
|
|
23
|
+
for (const candidate of candidates) {
|
|
24
|
+
const manifestPath = path.join(candidate, '.opencode', 'opencode.json');
|
|
25
|
+
if (fs.existsSync(manifestPath)) {
|
|
26
|
+
return candidate;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return DEFAULT_KIT_ROOT;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function print(line = '') {
|
|
34
|
+
process.stdout.write(`${line}\n`);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function readJsonIfPresent(filePath) {
|
|
38
|
+
if (!fs.existsSync(filePath)) {
|
|
39
|
+
return { exists: false, value: null, malformed: false };
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
try {
|
|
43
|
+
return {
|
|
44
|
+
exists: true,
|
|
45
|
+
value: JSON.parse(fs.readFileSync(filePath, 'utf8')),
|
|
46
|
+
malformed: false,
|
|
47
|
+
};
|
|
48
|
+
} catch {
|
|
49
|
+
return {
|
|
50
|
+
exists: true,
|
|
51
|
+
value: null,
|
|
52
|
+
malformed: true,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function resolveRuntimeContext(runtimeSummaryModulePath, runtimeRoot, state) {
|
|
58
|
+
try {
|
|
59
|
+
const { getRuntimeContext } = require(runtimeSummaryModulePath);
|
|
60
|
+
return getRuntimeContext(runtimeRoot, state);
|
|
61
|
+
} catch {
|
|
62
|
+
const workItemId = state?.work_item_id;
|
|
63
|
+
if (!workItemId) {
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const boardPath = path.join(runtimeRoot, '.opencode', 'work-items', workItemId, 'tasks.json');
|
|
68
|
+
if (!fs.existsSync(boardPath)) {
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
try {
|
|
73
|
+
const board = JSON.parse(fs.readFileSync(boardPath, 'utf8'));
|
|
74
|
+
const tasks = Array.isArray(board.tasks) ? board.tasks : [];
|
|
75
|
+
const activeStatuses = new Set(['claimed', 'in_progress', 'qa_in_progress']);
|
|
76
|
+
const activeTasks = tasks.filter((task) => activeStatuses.has(task.status));
|
|
77
|
+
const formattedActiveTasks = activeTasks.map((task) => {
|
|
78
|
+
if (task.status === 'qa_in_progress' && task.qa_owner) {
|
|
79
|
+
return `${task.task_id} (${task.status}, qa: ${task.qa_owner})`;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (task.primary_owner) {
|
|
83
|
+
return `${task.task_id} (${task.status}, primary: ${task.primary_owner})`;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return `${task.task_id} (${task.status})`;
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
return {
|
|
90
|
+
taskBoardSummary: {
|
|
91
|
+
total: tasks.length,
|
|
92
|
+
ready: tasks.filter((task) => task.status === 'ready').length,
|
|
93
|
+
active: activeTasks.length,
|
|
94
|
+
activeTasks: formattedActiveTasks,
|
|
95
|
+
},
|
|
96
|
+
};
|
|
97
|
+
} catch {
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function renderResumeHint(state, runtimeSummaryModulePath, statePath) {
|
|
104
|
+
const mode = state?.mode;
|
|
105
|
+
const stage = state?.current_stage;
|
|
106
|
+
const status = state?.status;
|
|
107
|
+
const owner = state?.current_owner;
|
|
108
|
+
const featureId = state?.feature_id;
|
|
109
|
+
const featureSlug = state?.feature_slug;
|
|
110
|
+
const workItemId = state?.work_item_id;
|
|
111
|
+
|
|
112
|
+
if (!mode || !stage || !status || !owner) {
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const runtimeRoot = path.dirname(path.dirname(path.resolve(statePath)));
|
|
117
|
+
const runtimeContext = mode === 'full' && workItemId
|
|
118
|
+
? resolveRuntimeContext(runtimeSummaryModulePath, runtimeRoot, state)
|
|
119
|
+
: null;
|
|
120
|
+
const boardSummary = runtimeContext?.taskBoardSummary ?? null;
|
|
121
|
+
const activeTasks = boardSummary?.activeTasks ?? [];
|
|
122
|
+
|
|
123
|
+
print('<workflow_resume_hint>');
|
|
124
|
+
print('OpenKit workflow resume context detected.');
|
|
125
|
+
print(`mode: ${mode}`);
|
|
126
|
+
print(`stage: ${stage}`);
|
|
127
|
+
print(`status: ${status}`);
|
|
128
|
+
print(`owner: ${owner}`);
|
|
129
|
+
if (featureId && featureSlug) {
|
|
130
|
+
print(`work item: ${featureId} (${featureSlug})`);
|
|
131
|
+
}
|
|
132
|
+
if (workItemId) {
|
|
133
|
+
print(`active work item id: ${workItemId}`);
|
|
134
|
+
}
|
|
135
|
+
if (boardSummary) {
|
|
136
|
+
print(`task board: ${boardSummary.total ?? 0} tasks | ready ${boardSummary.ready ?? 0} | active ${boardSummary.active ?? 0}`);
|
|
137
|
+
}
|
|
138
|
+
if (activeTasks.length > 0) {
|
|
139
|
+
print(`active tasks: ${activeTasks.join('; ')}`);
|
|
140
|
+
}
|
|
141
|
+
print('Read first: AGENTS.md -> context/navigation.md -> context/core/workflow.md -> .opencode/workflow-state.json');
|
|
142
|
+
print('Then load resume guidance from context/core/session-resume.md.');
|
|
143
|
+
if (mode === 'full' && activeTasks.length > 0) {
|
|
144
|
+
print('Parallel task support is not yet assumed safe by this hook; confirm with `node .opencode/workflow-state.js doctor` before relying on it.');
|
|
145
|
+
}
|
|
146
|
+
print('</workflow_resume_hint>');
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const projectRoot = process.env.OPENKIT_PROJECT_ROOT ? path.resolve(process.env.OPENKIT_PROJECT_ROOT) : process.cwd();
|
|
150
|
+
const statePath = process.env.OPENKIT_WORKFLOW_STATE
|
|
151
|
+
? path.resolve(process.env.OPENKIT_WORKFLOW_STATE)
|
|
152
|
+
: path.join(projectRoot, '.opencode', 'workflow-state.json');
|
|
153
|
+
const kitRoot = resolveKitRoot(projectRoot, statePath);
|
|
154
|
+
const metaSkillPath = path.join(kitRoot, 'skills', 'using-skills', 'SKILL.md');
|
|
155
|
+
const manifestPath = path.join(kitRoot, '.opencode', 'opencode.json');
|
|
156
|
+
const runtimeSummaryModulePath = path.join(kitRoot, '.opencode', 'lib', 'runtime-summary.js');
|
|
157
|
+
|
|
158
|
+
let kitName = 'OpenKit AI Software Factory';
|
|
159
|
+
let kitVersion = 'unknown';
|
|
160
|
+
let entryAgent = 'unknown';
|
|
161
|
+
let jsonHelperStatus = 'ok';
|
|
162
|
+
|
|
163
|
+
const manifestResult = readJsonIfPresent(manifestPath);
|
|
164
|
+
if (manifestResult.malformed) {
|
|
165
|
+
jsonHelperStatus = 'degraded';
|
|
166
|
+
} else if (manifestResult.value?.kit) {
|
|
167
|
+
kitName = manifestResult.value.kit.name || kitName;
|
|
168
|
+
kitVersion = manifestResult.value.kit.version || kitVersion;
|
|
169
|
+
entryAgent = manifestResult.value.kit.entryAgent || entryAgent;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
let skillStatus = 'missing';
|
|
173
|
+
if (process.env.OPENKIT_SESSION_START_NO_SKILL) {
|
|
174
|
+
skillStatus = 'skipped';
|
|
175
|
+
} else if (fs.existsSync(metaSkillPath)) {
|
|
176
|
+
skillStatus = 'loaded';
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const stateResult = readJsonIfPresent(statePath);
|
|
180
|
+
if (stateResult.malformed) {
|
|
181
|
+
jsonHelperStatus = 'degraded';
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
print('<openkit_runtime_status>');
|
|
185
|
+
print(`kit: ${kitName} v${kitVersion}`);
|
|
186
|
+
print(`entry agent: ${entryAgent}`);
|
|
187
|
+
print(`state file: ${statePath}`);
|
|
188
|
+
print(`startup skill: ${skillStatus}`);
|
|
189
|
+
print(`json helper: ${jsonHelperStatus}`);
|
|
190
|
+
print('help: node .opencode/workflow-state.js status');
|
|
191
|
+
print('doctor: node .opencode/workflow-state.js doctor');
|
|
192
|
+
print('show: node .opencode/workflow-state.js show');
|
|
193
|
+
print('</openkit_runtime_status>');
|
|
194
|
+
|
|
195
|
+
if (!process.env.OPENKIT_SESSION_START_NO_SKILL && fs.existsSync(metaSkillPath)) {
|
|
196
|
+
const metaSkill = fs.readFileSync(metaSkillPath, 'utf8');
|
|
197
|
+
print('<skill_system_instruction>');
|
|
198
|
+
print('You are running within the Open Kit AI Software Factory framework.');
|
|
199
|
+
print('Below are the rules for how you must discover and invoke your skills.');
|
|
200
|
+
print();
|
|
201
|
+
process.stdout.write(metaSkill);
|
|
202
|
+
if (!metaSkill.endsWith('\n')) {
|
|
203
|
+
print();
|
|
204
|
+
}
|
|
205
|
+
print('</skill_system_instruction>');
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
if (jsonHelperStatus === 'ok' && !stateResult.malformed && stateResult.value) {
|
|
209
|
+
renderResumeHint(stateResult.value, runtimeSummaryModulePath, statePath);
|
|
210
|
+
}
|
package/package.json
CHANGED
package/registry.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"registryVersion": 1,
|
|
4
4
|
"kit": {
|
|
5
5
|
"name": "OpenKit AI Software Factory",
|
|
6
|
-
"version": "0.
|
|
6
|
+
"version": "0.2.9",
|
|
7
7
|
"description": "Local component metadata for the checked-in OpenKit repository and the global-kit compatibility surface.",
|
|
8
8
|
"repositoryRoot": ".",
|
|
9
9
|
"productSurface": {
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
|
|
4
|
+
function listWindowsExecutableExtensions(env) {
|
|
5
|
+
const raw = env.PATHEXT ?? '.COM;.EXE;.BAT;.CMD';
|
|
6
|
+
const extensions = raw
|
|
7
|
+
.split(';')
|
|
8
|
+
.map((value) => value.trim().toLowerCase())
|
|
9
|
+
.filter(Boolean);
|
|
10
|
+
|
|
11
|
+
return extensions.length > 0 ? extensions : ['.com', '.exe', '.bat', '.cmd'];
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function isCommandAvailable(command, { env = process.env, platform = process.platform } = {}) {
|
|
15
|
+
if (typeof command !== 'string' || command.length === 0) {
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const pathValue = env.PATH ?? '';
|
|
20
|
+
if (pathValue.length === 0) {
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const segments = pathValue.split(path.delimiter).filter(Boolean);
|
|
25
|
+
const hasExtension = path.extname(command).length > 0;
|
|
26
|
+
const suffixes = platform === 'win32' && !hasExtension ? ['', ...listWindowsExecutableExtensions(env)] : [''];
|
|
27
|
+
|
|
28
|
+
for (const segment of segments) {
|
|
29
|
+
for (const suffix of suffixes) {
|
|
30
|
+
if (fs.existsSync(path.join(segment, `${command}${suffix}`))) {
|
|
31
|
+
return true;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return false;
|
|
37
|
+
}
|
package/src/global/doctor.js
CHANGED
|
@@ -2,12 +2,12 @@ import fs from 'node:fs';
|
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
|
|
4
4
|
import { readJsonIfPresent, validateGlobalInstallState } from './install-state.js';
|
|
5
|
-
import {
|
|
5
|
+
import { inspectWorkspaceMeta } from './workspace-state.js';
|
|
6
6
|
import { getGlobalPaths, getWorkspacePaths } from './paths.js';
|
|
7
|
+
import { isCommandAvailable } from '../command-detection.js';
|
|
7
8
|
|
|
8
9
|
function isOpenCodeAvailable(env = process.env) {
|
|
9
|
-
|
|
10
|
-
return pathValue.split(path.delimiter).some((segment) => segment && fs.existsSync(path.join(segment, 'opencode')));
|
|
10
|
+
return isCommandAvailable('opencode', { env });
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
function withGuidance(result, nextStep, recommendedCommand = null) {
|
|
@@ -59,8 +59,7 @@ export function inspectGlobalDoctor({ projectRoot = process.cwd(), env = process
|
|
|
59
59
|
issues.push('OpenCode executable is not available on PATH.');
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
-
const workspace =
|
|
63
|
-
ensureWorkspaceBootstrap({ projectRoot, env });
|
|
62
|
+
const workspace = inspectWorkspaceMeta({ projectRoot, env });
|
|
64
63
|
|
|
65
64
|
return withGuidance({
|
|
66
65
|
status: issues.length === 0 ? 'healthy' : 'workspace-ready-with-issues',
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
|
|
4
|
+
import { getOpenKitVersion } from '../version.js';
|
|
5
|
+
|
|
4
6
|
const GLOBAL_INSTALL_SCHEMA = 'openkit/global-install-state@1';
|
|
5
7
|
|
|
6
|
-
export function createGlobalInstallState({ kitVersion =
|
|
8
|
+
export function createGlobalInstallState({ kitVersion = getOpenKitVersion(), installedAt = new Date().toISOString(), profile = 'openkit' } = {}) {
|
|
7
9
|
return {
|
|
8
10
|
schema: GLOBAL_INSTALL_SCHEMA,
|
|
9
11
|
stateVersion: 1,
|
|
@@ -4,6 +4,7 @@ import { fileURLToPath } from 'node:url';
|
|
|
4
4
|
|
|
5
5
|
import { createGlobalInstallState, writeJson } from './install-state.js';
|
|
6
6
|
import { getGlobalPaths } from './paths.js';
|
|
7
|
+
import { getOpenKitVersion } from '../version.js';
|
|
7
8
|
|
|
8
9
|
const MODULE_DIR = path.dirname(fileURLToPath(import.meta.url));
|
|
9
10
|
const PACKAGE_ROOT = path.resolve(MODULE_DIR, '../..');
|
|
@@ -65,7 +66,7 @@ function createOpenCodeConfig() {
|
|
|
65
66
|
};
|
|
66
67
|
}
|
|
67
68
|
|
|
68
|
-
export function materializeGlobalInstall({ env = process.env, kitVersion =
|
|
69
|
+
export function materializeGlobalInstall({ env = process.env, kitVersion = getOpenKitVersion() } = {}) {
|
|
69
70
|
const paths = getGlobalPaths({ env });
|
|
70
71
|
|
|
71
72
|
removePathIfPresent(paths.kitRoot);
|
|
@@ -92,7 +93,7 @@ export function materializeGlobalInstall({ env = process.env, kitVersion = '0.1.
|
|
|
92
93
|
hooks: [
|
|
93
94
|
{
|
|
94
95
|
type: 'command',
|
|
95
|
-
command: `${path.join(paths.kitRoot, 'hooks', 'session-start')}`,
|
|
96
|
+
command: `${JSON.stringify(process.execPath)} ${JSON.stringify(path.join(paths.kitRoot, 'hooks', 'session-start.js'))}`,
|
|
96
97
|
async: false,
|
|
97
98
|
},
|
|
98
99
|
],
|
|
@@ -25,6 +25,53 @@ function writeJson(filePath, value) {
|
|
|
25
25
|
writeFile(filePath, `${JSON.stringify(value, null, 2)}\n`);
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
+
function readJson(filePath) {
|
|
29
|
+
return JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function isSymlink(filePath) {
|
|
33
|
+
try {
|
|
34
|
+
return fs.lstatSync(filePath).isSymbolicLink();
|
|
35
|
+
} catch {
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function isManagedWorkflowWrapper(content, { requiresAliasMap = false } = {}) {
|
|
41
|
+
if (typeof content !== 'string' || content.length === 0) {
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const sharedMarkers = [
|
|
46
|
+
"require('node:child_process')",
|
|
47
|
+
'spawnSync(process.execPath',
|
|
48
|
+
"'--state'",
|
|
49
|
+
"process.exit(typeof result.status === 'number' ? result.status : 1);",
|
|
50
|
+
];
|
|
51
|
+
|
|
52
|
+
if (!sharedMarkers.every((marker) => content.includes(marker))) {
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (!requiresAliasMap) {
|
|
57
|
+
return true;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return content.includes('const aliasMap = new Map([');
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function shouldRefreshRootWorkflowWrapper(filePath) {
|
|
64
|
+
if (!fs.existsSync(filePath)) {
|
|
65
|
+
return true;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
try {
|
|
69
|
+
return isManagedWorkflowWrapper(fs.readFileSync(filePath, 'utf8'), { requiresAliasMap: true });
|
|
70
|
+
} catch {
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
28
75
|
function relativeTarget(fromPath, toPath) {
|
|
29
76
|
return path.relative(path.dirname(fromPath), toPath) || '.';
|
|
30
77
|
}
|
|
@@ -93,8 +140,13 @@ export function ensureWorkspaceShim(paths) {
|
|
|
93
140
|
type: 'file',
|
|
94
141
|
});
|
|
95
142
|
|
|
143
|
+
if (fs.existsSync(paths.workflowStatePath)) {
|
|
144
|
+
writeJson(paths.workspaceShimWorkflowStatePath, readJson(paths.workflowStatePath));
|
|
145
|
+
}
|
|
146
|
+
|
|
96
147
|
const workflowCli = `#!/usr/bin/env node
|
|
97
|
-
|
|
148
|
+
// Generated by OpenKit workspace shim.
|
|
149
|
+
const { spawnSync } = require('node:child_process');
|
|
98
150
|
|
|
99
151
|
const args = process.argv.slice(2);
|
|
100
152
|
const result = spawnSync(process.execPath, [${JSON.stringify(path.join(paths.kitRoot, '.opencode', 'workflow-state.js'))}, '--state', ${JSON.stringify(paths.workflowStatePath)}, ...args], {
|
|
@@ -109,8 +161,9 @@ if (result.error) {
|
|
|
109
161
|
process.exit(typeof result.status === 'number' ? result.status : 1);
|
|
110
162
|
`;
|
|
111
163
|
|
|
112
|
-
|
|
113
|
-
|
|
164
|
+
const workspaceWrapperExists = fs.existsSync(paths.workspaceShimWorkflowCliPath);
|
|
165
|
+
writeFile(paths.workspaceShimWorkflowCliPath, workflowCli, 0o755);
|
|
166
|
+
if (!workspaceWrapperExists) {
|
|
114
167
|
createdPaths.push(paths.workspaceShimWorkflowCliPath);
|
|
115
168
|
}
|
|
116
169
|
|
|
@@ -126,22 +179,21 @@ process.exit(typeof result.status === 'number' ? result.status : 1);
|
|
|
126
179
|
type: 'dir',
|
|
127
180
|
});
|
|
128
181
|
|
|
129
|
-
createIfMissing(createdPaths, {
|
|
182
|
+
const rootWorkflowStateMode = createIfMissing(createdPaths, {
|
|
130
183
|
linkPath: path.join(paths.projectRoot, '.opencode', 'workflow-state.json'),
|
|
131
184
|
targetPath: paths.workspaceShimWorkflowStatePath,
|
|
132
185
|
type: 'file',
|
|
133
186
|
});
|
|
134
187
|
|
|
135
|
-
const
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
writeJson(opencodePackagePath, { type: 'module' });
|
|
139
|
-
createdPaths.push(opencodePackagePath);
|
|
188
|
+
const rootWorkflowStatePath = path.join(paths.projectRoot, '.opencode', 'workflow-state.json');
|
|
189
|
+
if (fs.existsSync(paths.workflowStatePath) && (rootWorkflowStateMode !== null || isSymlink(rootWorkflowStatePath))) {
|
|
190
|
+
writeJson(rootWorkflowStatePath, readJson(paths.workflowStatePath));
|
|
140
191
|
}
|
|
141
192
|
|
|
142
|
-
if (
|
|
193
|
+
if (shouldRefreshRootWorkflowWrapper(path.join(paths.projectRoot, '.opencode', 'workflow-state.js'))) {
|
|
143
194
|
const rootWorkflowCli = `#!/usr/bin/env node
|
|
144
|
-
|
|
195
|
+
// Generated by OpenKit workspace shim.
|
|
196
|
+
const { spawnSync } = require('node:child_process');
|
|
145
197
|
|
|
146
198
|
const rawArgs = process.argv.slice(2);
|
|
147
199
|
const command = rawArgs[0];
|
|
@@ -151,7 +203,7 @@ const aliasMap = new Map([
|
|
|
151
203
|
['-h', 'help'],
|
|
152
204
|
]);
|
|
153
205
|
const normalizedArgs = rawArgs.length === 0 ? ['help'] : [aliasMap.get(command) ?? command, ...rawArgs.slice(1)];
|
|
154
|
-
const result = spawnSync(process.execPath, [${JSON.stringify(paths.
|
|
206
|
+
const result = spawnSync(process.execPath, [${JSON.stringify(path.join(paths.kitRoot, '.opencode', 'workflow-state.js'))}, '--state', ${JSON.stringify(paths.workflowStatePath)}, ...normalizedArgs], {
|
|
155
207
|
stdio: 'inherit',
|
|
156
208
|
env: process.env,
|
|
157
209
|
});
|
|
@@ -164,8 +216,11 @@ process.exit(typeof result.status === 'number' ? result.status : 1);
|
|
|
164
216
|
`;
|
|
165
217
|
|
|
166
218
|
const rootWorkflowCliPath = path.join(paths.projectRoot, '.opencode', 'workflow-state.js');
|
|
219
|
+
const rootWrapperExists = fs.existsSync(rootWorkflowCliPath);
|
|
167
220
|
writeFile(rootWorkflowCliPath, rootWorkflowCli, 0o755);
|
|
168
|
-
|
|
221
|
+
if (!rootWrapperExists) {
|
|
222
|
+
createdPaths.push(rootWorkflowCliPath);
|
|
223
|
+
}
|
|
169
224
|
}
|
|
170
225
|
|
|
171
226
|
if (!fs.existsSync(path.join(paths.projectRoot, '.opencode', 'work-items'))) {
|
|
@@ -3,9 +3,110 @@ import path from 'node:path';
|
|
|
3
3
|
|
|
4
4
|
import { getWorkspacePaths } from './paths.js';
|
|
5
5
|
import { ensureWorkspaceShim } from './workspace-shim.js';
|
|
6
|
+
import { getOpenKitVersion } from '../version.js';
|
|
6
7
|
|
|
7
8
|
const WORKSPACE_STATE_SCHEMA = 'openkit/workspace-state@1';
|
|
8
9
|
|
|
10
|
+
function createPendingGate() {
|
|
11
|
+
return {
|
|
12
|
+
status: 'pending',
|
|
13
|
+
approved_by: null,
|
|
14
|
+
approved_at: null,
|
|
15
|
+
notes: null,
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function createEmptyArtifacts() {
|
|
20
|
+
return {
|
|
21
|
+
task_card: null,
|
|
22
|
+
brief: null,
|
|
23
|
+
spec: null,
|
|
24
|
+
architecture: null,
|
|
25
|
+
plan: null,
|
|
26
|
+
migration_report: null,
|
|
27
|
+
qa_report: null,
|
|
28
|
+
adr: [],
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function createDefaultRoutingProfile(mode, selectionReason) {
|
|
33
|
+
if (mode === 'quick') {
|
|
34
|
+
return {
|
|
35
|
+
work_intent: 'maintenance',
|
|
36
|
+
behavior_delta: 'preserve',
|
|
37
|
+
dominant_uncertainty: 'low_local',
|
|
38
|
+
scope_shape: 'local',
|
|
39
|
+
selection_reason: selectionReason,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (mode === 'migration') {
|
|
44
|
+
return {
|
|
45
|
+
work_intent: 'modernization',
|
|
46
|
+
behavior_delta: 'preserve',
|
|
47
|
+
dominant_uncertainty: 'compatibility',
|
|
48
|
+
scope_shape: 'adjacent',
|
|
49
|
+
selection_reason: selectionReason,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return {
|
|
54
|
+
work_intent: 'feature',
|
|
55
|
+
behavior_delta: 'extend',
|
|
56
|
+
dominant_uncertainty: 'product',
|
|
57
|
+
scope_shape: 'cross_boundary',
|
|
58
|
+
selection_reason: selectionReason,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function createEmptyApprovals(mode) {
|
|
63
|
+
if (mode === 'quick') {
|
|
64
|
+
return {
|
|
65
|
+
quick_verified: createPendingGate(),
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (mode === 'migration') {
|
|
70
|
+
return {
|
|
71
|
+
baseline_to_strategy: createPendingGate(),
|
|
72
|
+
strategy_to_upgrade: createPendingGate(),
|
|
73
|
+
upgrade_to_verify: createPendingGate(),
|
|
74
|
+
migration_verified: createPendingGate(),
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return {
|
|
79
|
+
pm_to_ba: createPendingGate(),
|
|
80
|
+
ba_to_architect: createPendingGate(),
|
|
81
|
+
architect_to_tech_lead: createPendingGate(),
|
|
82
|
+
tech_lead_to_fullstack: createPendingGate(),
|
|
83
|
+
fullstack_to_qa: createPendingGate(),
|
|
84
|
+
qa_to_done: createPendingGate(),
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export function createInitialWorkflowState({ mode = 'quick', selectionReason = 'Initialized by OpenKit global workspace bootstrap.' } = {}) {
|
|
89
|
+
const currentStage = mode === 'migration' ? 'migration_intake' : mode === 'full' ? 'full_intake' : 'quick_intake';
|
|
90
|
+
return {
|
|
91
|
+
feature_id: null,
|
|
92
|
+
feature_slug: null,
|
|
93
|
+
mode,
|
|
94
|
+
mode_reason: selectionReason,
|
|
95
|
+
routing_profile: createDefaultRoutingProfile(mode, selectionReason),
|
|
96
|
+
current_stage: currentStage,
|
|
97
|
+
status: 'idle',
|
|
98
|
+
current_owner: 'MasterOrchestrator',
|
|
99
|
+
artifacts: createEmptyArtifacts(),
|
|
100
|
+
approvals: createEmptyApprovals(mode),
|
|
101
|
+
issues: [],
|
|
102
|
+
retry_count: 0,
|
|
103
|
+
escalated_from: null,
|
|
104
|
+
escalation_reason: null,
|
|
105
|
+
updated_at: new Date().toISOString(),
|
|
106
|
+
work_item_id: null,
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
|
|
9
110
|
function writeJson(filePath, value) {
|
|
10
111
|
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
11
112
|
fs.writeFileSync(filePath, `${JSON.stringify(value, null, 2)}\n`, 'utf8');
|
|
@@ -15,7 +116,7 @@ function readJson(filePath) {
|
|
|
15
116
|
return JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
16
117
|
}
|
|
17
118
|
|
|
18
|
-
export function createWorkspaceMeta({ projectRoot, workspaceId, kitVersion =
|
|
119
|
+
export function createWorkspaceMeta({ projectRoot, workspaceId, kitVersion = getOpenKitVersion(), profile = 'openkit' }) {
|
|
19
120
|
return {
|
|
20
121
|
schema: WORKSPACE_STATE_SCHEMA,
|
|
21
122
|
stateVersion: 1,
|
|
@@ -51,6 +152,10 @@ export function ensureWorkspaceBootstrap(options = {}) {
|
|
|
51
152
|
});
|
|
52
153
|
}
|
|
53
154
|
|
|
155
|
+
if (!fs.existsSync(paths.workflowStatePath)) {
|
|
156
|
+
writeJson(paths.workflowStatePath, createInitialWorkflowState({}));
|
|
157
|
+
}
|
|
158
|
+
|
|
54
159
|
const shim = ensureWorkspaceShim(paths);
|
|
55
160
|
|
|
56
161
|
return {
|
|
@@ -67,3 +172,13 @@ export function readWorkspaceMeta(options = {}) {
|
|
|
67
172
|
index: readJson(paths.workItemIndexPath),
|
|
68
173
|
};
|
|
69
174
|
}
|
|
175
|
+
|
|
176
|
+
export function inspectWorkspaceMeta(options = {}) {
|
|
177
|
+
const paths = getWorkspacePaths(options);
|
|
178
|
+
|
|
179
|
+
return {
|
|
180
|
+
paths,
|
|
181
|
+
meta: fs.existsSync(paths.workspaceMetaPath) ? readJson(paths.workspaceMetaPath) : null,
|
|
182
|
+
index: fs.existsSync(paths.workItemIndexPath) ? readJson(paths.workItemIndexPath) : null,
|
|
183
|
+
};
|
|
184
|
+
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { getOpenKitVersion } from '../version.js'
|
|
2
|
+
|
|
1
3
|
export const INSTALL_STATE_SCHEMA = "openkit/install-state@1"
|
|
2
4
|
|
|
3
5
|
const MANAGED_STATUSES = new Set(["managed", "materialized"])
|
|
@@ -24,7 +26,7 @@ function isArray(value) {
|
|
|
24
26
|
}
|
|
25
27
|
|
|
26
28
|
export function createInstallState({
|
|
27
|
-
kitVersion =
|
|
29
|
+
kitVersion = getOpenKitVersion(),
|
|
28
30
|
profile = "openkit-core",
|
|
29
31
|
managedAssets = [],
|
|
30
32
|
adoptedAssets = [],
|
|
@@ -5,6 +5,7 @@ import { fileURLToPath } from "node:url"
|
|
|
5
5
|
import { createInstallState } from "./install-state.js"
|
|
6
6
|
import { applyOpenKitMergePolicy } from "./merge-policy.js"
|
|
7
7
|
import { createMaterializationConflict, qualifyMergeConflicts } from "./conflicts.js"
|
|
8
|
+
import { getOpenKitVersion } from "../version.js"
|
|
8
9
|
|
|
9
10
|
const ROOT_MANIFEST_ASSET_ID = "runtime.opencode-manifest"
|
|
10
11
|
const ROOT_MANIFEST_PATH = "opencode.json"
|
|
@@ -40,7 +41,7 @@ function removeFileIfPresent(filePath) {
|
|
|
40
41
|
}
|
|
41
42
|
}
|
|
42
43
|
|
|
43
|
-
export function materializeInstall(projectRoot, { kitVersion =
|
|
44
|
+
export function materializeInstall(projectRoot, { kitVersion = getOpenKitVersion(), now } = {}) {
|
|
44
45
|
const desiredRootManifest = readTemplate("assets/opencode.json.template")
|
|
45
46
|
const rootManifestPath = path.join(projectRoot, ROOT_MANIFEST_PATH)
|
|
46
47
|
const installStatePath = path.join(projectRoot, INSTALL_STATE_PATH)
|
package/src/runtime/doctor.js
CHANGED
|
@@ -3,6 +3,7 @@ import path from 'node:path';
|
|
|
3
3
|
|
|
4
4
|
import { validateInstallState } from '../install/install-state.js';
|
|
5
5
|
import { discoverProjectShape } from '../install/discovery.js';
|
|
6
|
+
import { isCommandAvailable } from '../command-detection.js';
|
|
6
7
|
|
|
7
8
|
const EXPECTED_MANAGED_ASSETS = {
|
|
8
9
|
'runtime.opencode-manifest': {
|
|
@@ -52,15 +53,7 @@ function readJsonIfPresent(filePath) {
|
|
|
52
53
|
}
|
|
53
54
|
|
|
54
55
|
function defaultIsOpenCodeAvailable() {
|
|
55
|
-
|
|
56
|
-
return pathValue.split(path.delimiter).some((segment) => {
|
|
57
|
-
if (!segment) {
|
|
58
|
-
return false;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
const candidate = path.join(segment, 'opencode');
|
|
62
|
-
return fs.existsSync(candidate);
|
|
63
|
-
});
|
|
56
|
+
return isCommandAvailable('opencode');
|
|
64
57
|
}
|
|
65
58
|
|
|
66
59
|
export function inspectManagedDoctor({
|
package/src/version.js
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
|
|
5
|
+
const MODULE_DIR = path.dirname(fileURLToPath(import.meta.url));
|
|
6
|
+
const PACKAGE_JSON_PATH = path.resolve(MODULE_DIR, '..', 'package.json');
|
|
7
|
+
|
|
8
|
+
let cachedVersion = null;
|
|
9
|
+
|
|
10
|
+
export function getOpenKitVersion() {
|
|
11
|
+
if (cachedVersion !== null) {
|
|
12
|
+
return cachedVersion;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
try {
|
|
16
|
+
const packageJson = JSON.parse(fs.readFileSync(PACKAGE_JSON_PATH, 'utf8'));
|
|
17
|
+
cachedVersion = typeof packageJson.version === 'string' && packageJson.version.length > 0
|
|
18
|
+
? packageJson.version
|
|
19
|
+
: 'unknown';
|
|
20
|
+
} catch {
|
|
21
|
+
cachedVersion = 'unknown';
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return cachedVersion;
|
|
25
|
+
}
|
|
@@ -6,6 +6,8 @@ import os from 'node:os';
|
|
|
6
6
|
import path from 'node:path';
|
|
7
7
|
import { fileURLToPath } from 'node:url';
|
|
8
8
|
|
|
9
|
+
import { inspectGlobalDoctor } from '../../src/global/doctor.js';
|
|
10
|
+
|
|
9
11
|
const __filename = fileURLToPath(import.meta.url);
|
|
10
12
|
const __dirname = path.dirname(__filename);
|
|
11
13
|
const worktreeRoot = path.resolve(__dirname, '..', '..');
|
|
@@ -96,6 +98,8 @@ test('openkit install-global materializes global kit and profile files', () => {
|
|
|
96
98
|
assert.equal(fs.existsSync(path.join(profileRoot, 'opencode.json')), true);
|
|
97
99
|
assert.equal(readJson(path.join(profileRoot, 'opencode.json')).default_agent, 'master-orchestrator');
|
|
98
100
|
assert.equal(fs.existsSync(path.join(kitRoot, 'opencode.json')), true);
|
|
101
|
+
assert.match(readJson(path.join(kitRoot, 'install-state.json')).kit.version, /^0\.2\.9$/);
|
|
102
|
+
assert.match(readJson(path.join(profileRoot, 'hooks.json')).hooks.SessionStart[0].hooks[0].command, /session-start\.js/);
|
|
99
103
|
});
|
|
100
104
|
|
|
101
105
|
test('openkit init and install remain compatibility aliases for install-global', () => {
|
|
@@ -140,7 +144,7 @@ test('openkit doctor reports install-missing when global install is absent', ()
|
|
|
140
144
|
assert.match(result.stdout, /Recommended command: openkit run/);
|
|
141
145
|
});
|
|
142
146
|
|
|
143
|
-
test('openkit doctor reports healthy
|
|
147
|
+
test('openkit doctor reports healthy without mutating workspace metadata', () => {
|
|
144
148
|
const tempHome = makeTempDir();
|
|
145
149
|
const projectRoot = makeTempDir();
|
|
146
150
|
const fakeBinDir = path.join(tempHome, 'bin');
|
|
@@ -168,12 +172,8 @@ test('openkit doctor reports healthy after global install and bootstraps workspa
|
|
|
168
172
|
assert.match(result.stdout, /Workspace root:/);
|
|
169
173
|
assert.match(result.stdout, /Next: Run openkit run/);
|
|
170
174
|
assert.match(result.stdout, /Recommended command: openkit run/);
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
const workspaceEntries = fs.readdirSync(workspacesRoot);
|
|
174
|
-
assert.equal(workspaceEntries.length, 1);
|
|
175
|
-
const workspaceMetaPath = path.join(workspacesRoot, workspaceEntries[0], 'openkit', 'workspace.json');
|
|
176
|
-
assert.equal(fs.existsSync(workspaceMetaPath), true);
|
|
175
|
+
assert.equal(fs.existsSync(path.join(tempHome, 'workspaces')), false);
|
|
176
|
+
assert.equal(fs.existsSync(path.join(projectRoot, '.opencode')), false);
|
|
177
177
|
});
|
|
178
178
|
|
|
179
179
|
test('openkit run launches opencode with the global profile and workspace env', () => {
|
|
@@ -335,11 +335,76 @@ test('openkit run does not overwrite existing repo-local workflow files when cre
|
|
|
335
335
|
assert.equal(result.status, 0);
|
|
336
336
|
assert.equal(fs.readFileSync(path.join(projectRoot, 'AGENTS.md'), 'utf8'), 'project agents\n');
|
|
337
337
|
assert.equal(fs.readFileSync(path.join(projectRoot, 'context', 'core', 'workflow.md'), 'utf8'), 'project workflow\n');
|
|
338
|
-
assert.equal(fs.readFileSync(path.join(projectRoot, '.opencode', 'workflow-state.json'), 'utf8'), '{"project":true}\n');
|
|
339
338
|
assert.equal(fs.readFileSync(path.join(projectRoot, '.opencode', 'workflow-state.js'), 'utf8'), '#!/usr/bin/env node\n');
|
|
339
|
+
assert.equal(fs.readFileSync(path.join(projectRoot, '.opencode', 'workflow-state.json'), 'utf8'), '{"project":true}\n');
|
|
340
340
|
assert.equal(fs.existsSync(path.join(projectRoot, '.opencode', 'openkit', 'AGENTS.md')), true);
|
|
341
341
|
});
|
|
342
342
|
|
|
343
|
+
test('openkit run refreshes managed wrappers when the workspace location changes', () => {
|
|
344
|
+
const tempHomeA = makeTempDir();
|
|
345
|
+
const tempHomeB = makeTempDir();
|
|
346
|
+
const projectRoot = makeTempDir();
|
|
347
|
+
const fakeBinDirA = path.join(tempHomeA, 'bin');
|
|
348
|
+
const fakeBinDirB = path.join(tempHomeB, 'bin');
|
|
349
|
+
|
|
350
|
+
writeExecutable(path.join(fakeBinDirA, 'opencode'), '#!/bin/sh\nexit 0\n');
|
|
351
|
+
writeExecutable(path.join(fakeBinDirB, 'opencode'), '#!/bin/sh\nexit 0\n');
|
|
352
|
+
|
|
353
|
+
let result = runCli(['run'], {
|
|
354
|
+
cwd: projectRoot,
|
|
355
|
+
env: {
|
|
356
|
+
...process.env,
|
|
357
|
+
OPENCODE_HOME: tempHomeA,
|
|
358
|
+
PATH: `${fakeBinDirA}${path.delimiter}${process.env.PATH}`,
|
|
359
|
+
},
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
assert.equal(result.status, 0);
|
|
363
|
+
|
|
364
|
+
result = runCli(['run'], {
|
|
365
|
+
cwd: projectRoot,
|
|
366
|
+
env: {
|
|
367
|
+
...process.env,
|
|
368
|
+
OPENCODE_HOME: tempHomeB,
|
|
369
|
+
PATH: `${fakeBinDirB}${path.delimiter}${process.env.PATH}`,
|
|
370
|
+
},
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
assert.equal(result.status, 0);
|
|
374
|
+
|
|
375
|
+
const workspaceWrapper = fs.readFileSync(path.join(projectRoot, '.opencode', 'openkit', 'workflow-state.js'), 'utf8');
|
|
376
|
+
assert.match(workspaceWrapper, new RegExp(tempHomeB.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')));
|
|
377
|
+
assert.doesNotMatch(workspaceWrapper, new RegExp(tempHomeA.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')));
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
test('openkit doctor recognizes opencode.cmd on Windows-style PATH', () => {
|
|
381
|
+
const tempHome = makeTempDir();
|
|
382
|
+
const projectRoot = makeTempDir();
|
|
383
|
+
const fakeBinDir = path.join(tempHome, 'bin');
|
|
384
|
+
writeExecutable(path.join(fakeBinDir, 'opencode.cmd'), '@echo off\nexit /b 0\n');
|
|
385
|
+
|
|
386
|
+
const installResult = runCli(['install-global'], {
|
|
387
|
+
env: {
|
|
388
|
+
...process.env,
|
|
389
|
+
OPENCODE_HOME: tempHome,
|
|
390
|
+
},
|
|
391
|
+
});
|
|
392
|
+
assert.equal(installResult.status, 0);
|
|
393
|
+
|
|
394
|
+
const doctor = inspectGlobalDoctor({
|
|
395
|
+
projectRoot,
|
|
396
|
+
env: {
|
|
397
|
+
...process.env,
|
|
398
|
+
OPENCODE_HOME: tempHome,
|
|
399
|
+
PATH: fakeBinDir,
|
|
400
|
+
PATHEXT: '.CMD;.EXE',
|
|
401
|
+
},
|
|
402
|
+
});
|
|
403
|
+
|
|
404
|
+
assert.equal(doctor.status, 'workspace-ready-with-issues');
|
|
405
|
+
assert.match(doctor.issues.join('\n'), /OpenCode executable is not available on PATH/);
|
|
406
|
+
});
|
|
407
|
+
|
|
343
408
|
test('openkit run cleans root compatibility shims when created files are removed', () => {
|
|
344
409
|
const tempHome = makeTempDir();
|
|
345
410
|
const projectRoot = makeTempDir();
|
|
@@ -367,7 +432,7 @@ test('openkit run cleans root compatibility shims when created files are removed
|
|
|
367
432
|
assert.equal(fs.existsSync(path.join(projectRoot, '.opencode', 'openkit', 'AGENTS.md')), true);
|
|
368
433
|
});
|
|
369
434
|
|
|
370
|
-
test('openkit run creates
|
|
435
|
+
test('openkit run creates CommonJS workflow wrappers without module-boundary warnings', () => {
|
|
371
436
|
const tempHome = makeTempDir();
|
|
372
437
|
const projectRoot = makeTempDir();
|
|
373
438
|
const fakeBinDir = path.join(tempHome, 'bin');
|
|
@@ -384,11 +449,24 @@ test('openkit run creates a module-aware root workflow wrapper with alias suppor
|
|
|
384
449
|
});
|
|
385
450
|
|
|
386
451
|
assert.equal(result.status, 0);
|
|
387
|
-
assert.deepEqual(readJson(path.join(projectRoot, '.opencode', 'package.json')), { type: 'module' });
|
|
388
452
|
|
|
389
|
-
const
|
|
390
|
-
assert.match(
|
|
391
|
-
assert.match(
|
|
453
|
+
const rootWrapper = fs.readFileSync(path.join(projectRoot, '.opencode', 'workflow-state.js'), 'utf8');
|
|
454
|
+
assert.match(rootWrapper, /const \{ spawnSync \} = require\('node:child_process'\);/);
|
|
455
|
+
assert.match(rootWrapper, /\['get', 'show'\]/);
|
|
456
|
+
assert.match(rootWrapper, /\['--help', 'help'\]/);
|
|
457
|
+
|
|
458
|
+
const workspaceWrapper = fs.readFileSync(path.join(projectRoot, '.opencode', 'openkit', 'workflow-state.js'), 'utf8');
|
|
459
|
+
assert.match(workspaceWrapper, /const \{ spawnSync \} = require\('node:child_process'\);/);
|
|
460
|
+
assert.doesNotMatch(workspaceWrapper, /import \{ spawnSync \} from 'node:child_process';/);
|
|
461
|
+
|
|
462
|
+
const wrapperRun = spawnSync(process.execPath, ['.opencode/openkit/workflow-state.js', 'help'], {
|
|
463
|
+
cwd: projectRoot,
|
|
464
|
+
encoding: 'utf8',
|
|
465
|
+
});
|
|
466
|
+
|
|
467
|
+
assert.equal(wrapperRun.status, 0);
|
|
468
|
+
assert.match(wrapperRun.stdout, /Usage:/);
|
|
469
|
+
assert.doesNotMatch(wrapperRun.stderr, /MODULE_TYPELESS_PACKAGE_JSON/);
|
|
392
470
|
});
|
|
393
471
|
|
|
394
472
|
test('openkit run reports missing opencode after first-time setup completes', () => {
|
|
@@ -421,7 +499,7 @@ test('openkit run blocks on invalid global install state and recommends upgrade'
|
|
|
421
499
|
`${JSON.stringify({
|
|
422
500
|
schema: 'wrong-schema',
|
|
423
501
|
stateVersion: 1,
|
|
424
|
-
kit: { name: 'OpenKit', version: '0.
|
|
502
|
+
kit: { name: 'OpenKit', version: '0.2.9' },
|
|
425
503
|
installation: {
|
|
426
504
|
profile: 'openkit',
|
|
427
505
|
status: 'installed',
|
|
@@ -69,6 +69,11 @@ test('global doctor reports next steps for healthy installs', () => {
|
|
|
69
69
|
assert.equal(result.status, 'healthy');
|
|
70
70
|
assert.equal(result.nextStep, 'Run openkit run.');
|
|
71
71
|
assert.equal(result.recommendedCommand, 'openkit run');
|
|
72
|
+
assert.equal(result.workspace.paths.workspaceRoot.includes('workspaces'), true);
|
|
73
|
+
assert.equal(result.workspace.meta, null);
|
|
74
|
+
assert.equal(result.workspace.index, null);
|
|
75
|
+
assert.equal(fs.existsSync(path.join(projectRoot, '.opencode')), false);
|
|
76
|
+
assert.equal(fs.existsSync(path.join(tempHome, 'workspaces')), false);
|
|
72
77
|
});
|
|
73
78
|
|
|
74
79
|
test('global doctor recommends upgrade for invalid installs', () => {
|
|
@@ -80,7 +85,7 @@ test('global doctor recommends upgrade for invalid installs', () => {
|
|
|
80
85
|
stateVersion: 1,
|
|
81
86
|
kit: {
|
|
82
87
|
name: 'OpenKit',
|
|
83
|
-
version: '0.
|
|
88
|
+
version: '0.2.9',
|
|
84
89
|
},
|
|
85
90
|
installation: {
|
|
86
91
|
profile: 'openkit',
|
|
@@ -127,4 +132,6 @@ test('global doctor reports workspace issues with guidance', () => {
|
|
|
127
132
|
assert.equal(result.nextStep, 'Review the issues above before relying on this workspace.');
|
|
128
133
|
assert.equal(result.recommendedCommand, null);
|
|
129
134
|
assert.match(result.issues.join('\n'), /OpenCode executable is not available on PATH/);
|
|
135
|
+
assert.equal(result.workspace.meta, null);
|
|
136
|
+
assert.equal(fs.existsSync(path.join(projectRoot, '.opencode')), false);
|
|
130
137
|
});
|
|
@@ -44,6 +44,7 @@ test('ensureGlobalInstall returns none when install is healthy', () => {
|
|
|
44
44
|
assert.equal(result.action, 'none');
|
|
45
45
|
assert.equal(result.installed, false);
|
|
46
46
|
assert.equal(result.doctor.status, 'healthy');
|
|
47
|
+
assert.equal(fs.existsSync(path.join(tempHome, 'workspaces')), false);
|
|
47
48
|
});
|
|
48
49
|
|
|
49
50
|
test('ensureGlobalInstall materializes the global install when it is missing', () => {
|
|
@@ -80,7 +81,7 @@ test('ensureGlobalInstall returns blocked when install state is invalid', () =>
|
|
|
80
81
|
stateVersion: 1,
|
|
81
82
|
kit: {
|
|
82
83
|
name: 'OpenKit',
|
|
83
|
-
version: '0.
|
|
84
|
+
version: '0.2.9',
|
|
84
85
|
},
|
|
85
86
|
installation: {
|
|
86
87
|
profile: 'openkit',
|
|
@@ -38,7 +38,7 @@ function materializeManagedInstall(projectRoot) {
|
|
|
38
38
|
stateVersion: 1,
|
|
39
39
|
kit: {
|
|
40
40
|
name: 'OpenKit',
|
|
41
|
-
version: '0.
|
|
41
|
+
version: '0.2.9',
|
|
42
42
|
},
|
|
43
43
|
installation: {
|
|
44
44
|
profile: 'openkit-core',
|
|
@@ -110,7 +110,7 @@ test('doctor reports install incomplete for a partial install when install state
|
|
|
110
110
|
stateVersion: 1,
|
|
111
111
|
kit: {
|
|
112
112
|
name: 'OpenKit',
|
|
113
|
-
version: '0.
|
|
113
|
+
version: '0.2.9',
|
|
114
114
|
},
|
|
115
115
|
installation: {
|
|
116
116
|
profile: 'openkit-core',
|
|
@@ -187,7 +187,7 @@ test('doctor reports drift for managed install-state assets it owns in phase 1',
|
|
|
187
187
|
stateVersion: 1,
|
|
188
188
|
kit: {
|
|
189
189
|
name: 'OpenKit',
|
|
190
|
-
version: '0.
|
|
190
|
+
version: '0.2.9',
|
|
191
191
|
},
|
|
192
192
|
installation: {
|
|
193
193
|
profile: 'custom-profile',
|
|
@@ -323,7 +323,7 @@ test('doctor does not report healthy when an adopted root manifest is incompatib
|
|
|
323
323
|
stateVersion: 1,
|
|
324
324
|
kit: {
|
|
325
325
|
name: 'OpenKit',
|
|
326
|
-
version: '0.
|
|
326
|
+
version: '0.2.9',
|
|
327
327
|
},
|
|
328
328
|
installation: {
|
|
329
329
|
profile: 'openkit-core',
|
|
@@ -390,7 +390,7 @@ test('doctor can report healthy when an adopted root manifest still satisfies th
|
|
|
390
390
|
stateVersion: 1,
|
|
391
391
|
kit: {
|
|
392
392
|
name: 'OpenKit',
|
|
393
|
-
version: '0.
|
|
393
|
+
version: '0.2.9',
|
|
394
394
|
},
|
|
395
395
|
installation: {
|
|
396
396
|
profile: 'openkit-core',
|