@duypham93/openkit 0.2.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/.opencode/README.md +47 -0
- package/.opencode/install-manifest.json +41 -0
- package/.opencode/lib/artifact-scaffolder.js +111 -0
- package/.opencode/lib/contract-consistency.js +218 -0
- package/.opencode/lib/parallel-execution-rules.js +261 -0
- package/.opencode/lib/runtime-paths.js +95 -0
- package/.opencode/lib/runtime-summary.js +82 -0
- package/.opencode/lib/state-guard.js +99 -0
- package/.opencode/lib/task-board-rules.js +375 -0
- package/.opencode/lib/work-item-store.js +280 -0
- package/.opencode/lib/workflow-state-controller.js +1739 -0
- package/.opencode/lib/workflow-state-rules.js +331 -0
- package/.opencode/opencode.json +93 -0
- package/.opencode/package.json +3 -0
- package/.opencode/tests/artifact-scaffolder.test.js +733 -0
- package/.opencode/tests/multi-work-item-runtime.test.js +369 -0
- package/.opencode/tests/parallel-execution-runtime.test.js +259 -0
- package/.opencode/tests/session-start-hook.test.js +357 -0
- package/.opencode/tests/state-guard.test.js +124 -0
- package/.opencode/tests/task-board-rules.test.js +204 -0
- package/.opencode/tests/work-item-store.test.js +380 -0
- package/.opencode/tests/workflow-behavior.test.js +149 -0
- package/.opencode/tests/workflow-contract-consistency.test.js +387 -0
- package/.opencode/tests/workflow-state-cli.test.js +1275 -0
- package/.opencode/tests/workflow-state-controller.test.js +1038 -0
- package/.opencode/work-items/feature-001/state.json +70 -0
- package/.opencode/work-items/index.json +13 -0
- package/.opencode/workflow-state.js +489 -0
- package/.opencode/workflow-state.json +70 -0
- package/AGENTS.md +265 -0
- package/README.md +401 -0
- package/agents/architect-agent.md +63 -0
- package/agents/ba-agent.md +56 -0
- package/agents/code-reviewer.md +77 -0
- package/agents/fullstack-agent.md +115 -0
- package/agents/master-orchestrator.md +60 -0
- package/agents/pm-agent.md +56 -0
- package/agents/qa-agent.md +124 -0
- package/agents/tech-lead-agent.md +60 -0
- package/assets/install-bundle/README.md +7 -0
- package/assets/install-bundle/opencode/README.md +11 -0
- package/assets/install-bundle/opencode/agents/ArchitectAgent.md +63 -0
- package/assets/install-bundle/opencode/agents/BAAgent.md +56 -0
- package/assets/install-bundle/opencode/agents/CodeReviewer.md +77 -0
- package/assets/install-bundle/opencode/agents/FullstackAgent.md +115 -0
- package/assets/install-bundle/opencode/agents/MasterOrchestrator.md +60 -0
- package/assets/install-bundle/opencode/agents/PMAgent.md +56 -0
- package/assets/install-bundle/opencode/agents/QAAgent.md +124 -0
- package/assets/install-bundle/opencode/agents/TechLeadAgent.md +60 -0
- package/assets/install-bundle/opencode/commands/brainstorm.md +44 -0
- package/assets/install-bundle/opencode/commands/delivery.md +45 -0
- package/assets/install-bundle/opencode/commands/execute-plan.md +44 -0
- package/assets/install-bundle/opencode/commands/migrate.md +61 -0
- package/assets/install-bundle/opencode/commands/quick-task.md +45 -0
- package/assets/install-bundle/opencode/commands/task.md +46 -0
- package/assets/install-bundle/opencode/commands/write-plan.md +50 -0
- package/assets/install-bundle/opencode/context/core/lane-selection.md +54 -0
- package/assets/install-bundle/opencode/skills/brainstorming/SKILL.md +51 -0
- package/assets/install-bundle/opencode/skills/code-review/SKILL.md +48 -0
- package/assets/install-bundle/opencode/skills/subagent-driven-development/SKILL.md +79 -0
- package/assets/install-bundle/opencode/skills/systematic-debugging/SKILL.md +61 -0
- package/assets/install-bundle/opencode/skills/test-driven-development/SKILL.md +48 -0
- package/assets/install-bundle/opencode/skills/using-skills/SKILL.md +39 -0
- package/assets/install-bundle/opencode/skills/verification-before-completion/SKILL.md +137 -0
- package/assets/install-bundle/opencode/skills/writing-plans/SKILL.md +68 -0
- package/assets/install-bundle/opencode/skills/writing-specs/SKILL.md +47 -0
- package/assets/opencode.json.template +11 -0
- package/assets/openkit-install.json.template +19 -0
- package/bin/openkit.js +9 -0
- package/commands/brainstorm.md +44 -0
- package/commands/delivery.md +45 -0
- package/commands/execute-plan.md +44 -0
- package/commands/migrate.md +61 -0
- package/commands/quick-task.md +45 -0
- package/commands/task.md +46 -0
- package/commands/write-plan.md +50 -0
- package/context/core/approval-gates.md +146 -0
- package/context/core/code-quality.md +42 -0
- package/context/core/issue-routing.md +85 -0
- package/context/core/lane-selection.md +54 -0
- package/context/core/project-config.md +143 -0
- package/context/core/session-resume.md +85 -0
- package/context/core/workflow-state-schema.md +224 -0
- package/context/core/workflow.md +442 -0
- package/context/navigation.md +94 -0
- package/docs/adr/README.md +6 -0
- package/docs/architecture/2026-03-20-task-intake-dashboard.md +54 -0
- package/docs/architecture/README.md +7 -0
- package/docs/briefs/2026-03-20-task-intake-dashboard.md +48 -0
- package/docs/briefs/README.md +7 -0
- package/docs/governance/README.md +25 -0
- package/docs/governance/adr-policy.md +27 -0
- package/docs/governance/definition-of-done.md +17 -0
- package/docs/governance/naming-conventions.md +21 -0
- package/docs/governance/severity-levels.md +12 -0
- package/docs/maintainer/README.md +51 -0
- package/docs/operations/README.md +79 -0
- package/docs/operations/internal-records/2026-03-24-release-checklist.md +79 -0
- package/docs/operations/internal-records/2026-03-24-simplified-install-ux.md +36 -0
- package/docs/operations/internal-records/README.md +18 -0
- package/docs/operations/runbooks/README.md +23 -0
- package/docs/operations/runbooks/openkit-daily-usage.md +288 -0
- package/docs/operations/runbooks/workflow-state-smoke-tests.md +302 -0
- package/docs/operator/README.md +50 -0
- package/docs/plans/2026-03-20-task-intake-dashboard.md +49 -0
- package/docs/plans/2026-03-21-openkit-full-delivery-multi-task-runtime.md +521 -0
- package/docs/plans/2026-03-23-openkit-global-install-runtime.md +157 -0
- package/docs/plans/README.md +7 -0
- package/docs/qa/2026-03-20-task-intake-dashboard.md +41 -0
- package/docs/qa/README.md +7 -0
- package/docs/specs/2026-03-20-task-intake-dashboard.md +50 -0
- package/docs/specs/2026-03-21-openkit-full-delivery-multi-task-runtime.md +462 -0
- package/docs/specs/README.md +7 -0
- package/docs/templates/README.md +36 -0
- package/docs/templates/adr-template.md +18 -0
- package/docs/templates/architecture-template.md +31 -0
- package/docs/templates/implementation-plan-template.md +32 -0
- package/docs/templates/migration-baseline-checklist.md +48 -0
- package/docs/templates/migration-plan-template.md +52 -0
- package/docs/templates/migration-report-template.md +74 -0
- package/docs/templates/migration-verify-checklist.md +39 -0
- package/docs/templates/product-brief-template.md +32 -0
- package/docs/templates/qa-report-template.md +37 -0
- package/docs/templates/quick-task-template.md +36 -0
- package/docs/templates/spec-template.md +31 -0
- package/hooks/hooks.json +16 -0
- package/hooks/session-start +162 -0
- package/package.json +24 -0
- package/registry.json +328 -0
- package/skills/brainstorming/SKILL.md +51 -0
- package/skills/code-review/SKILL.md +48 -0
- package/skills/subagent-driven-development/SKILL.md +79 -0
- package/skills/systematic-debugging/SKILL.md +61 -0
- package/skills/test-driven-development/SKILL.md +48 -0
- package/skills/using-skills/SKILL.md +39 -0
- package/skills/verification-before-completion/SKILL.md +137 -0
- package/skills/writing-plans/SKILL.md +68 -0
- package/skills/writing-specs/SKILL.md +47 -0
- package/src/audit/vietnamese-detection.js +259 -0
- package/src/cli/commands/detect-vietnamese.js +24 -0
- package/src/cli/commands/doctor.js +33 -0
- package/src/cli/commands/help.js +33 -0
- package/src/cli/commands/init.js +25 -0
- package/src/cli/commands/install-global.js +26 -0
- package/src/cli/commands/install.js +25 -0
- package/src/cli/commands/run.js +63 -0
- package/src/cli/commands/uninstall.js +32 -0
- package/src/cli/commands/upgrade.js +25 -0
- package/src/cli/conflict-output.js +19 -0
- package/src/cli/index.js +56 -0
- package/src/global/doctor.js +101 -0
- package/src/global/ensure-install.js +32 -0
- package/src/global/install-state.js +73 -0
- package/src/global/launcher.js +51 -0
- package/src/global/materialize.js +123 -0
- package/src/global/paths.js +85 -0
- package/src/global/uninstall.js +25 -0
- package/src/global/workspace-state.js +63 -0
- package/src/install/asset-manifest.js +284 -0
- package/src/install/conflicts.js +43 -0
- package/src/install/discovery.js +138 -0
- package/src/install/install-state.js +136 -0
- package/src/install/materialize.js +158 -0
- package/src/install/merge-policy.js +145 -0
- package/src/runtime/doctor.js +281 -0
- package/src/runtime/launcher.js +49 -0
- package/src/runtime/opencode-layering.js +135 -0
- package/src/runtime/openkit-managed-summary.js +27 -0
- package/tests/cli/openkit-cli.test.js +417 -0
- package/tests/global/doctor.test.js +130 -0
- package/tests/global/ensure-install.test.js +105 -0
- package/tests/install/discovery.test.js +124 -0
- package/tests/install/install-state.test.js +346 -0
- package/tests/install/materialize.test.js +244 -0
- package/tests/install/merge-policy.test.js +177 -0
- package/tests/runtime/doctor.test.js +430 -0
- package/tests/runtime/launcher.test.js +230 -0
- package/tests/runtime/module-boundary.test.js +16 -0
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import fs from 'node:fs';
|
|
4
|
+
import os from 'node:os';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
|
|
7
|
+
import { ensureGlobalInstall } from '../../src/global/ensure-install.js';
|
|
8
|
+
import { materializeGlobalInstall } from '../../src/global/materialize.js';
|
|
9
|
+
|
|
10
|
+
function makeTempDir() {
|
|
11
|
+
return fs.mkdtempSync(path.join(os.tmpdir(), 'openkit-global-ensure-'));
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function writeJson(filePath, value) {
|
|
15
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
16
|
+
fs.writeFileSync(filePath, `${JSON.stringify(value, null, 2)}\n`, 'utf8');
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
test('ensureGlobalInstall returns none when install is healthy', () => {
|
|
20
|
+
const tempHome = makeTempDir();
|
|
21
|
+
const projectRoot = makeTempDir();
|
|
22
|
+
const fakeBinDir = path.join(tempHome, 'bin');
|
|
23
|
+
|
|
24
|
+
materializeGlobalInstall({
|
|
25
|
+
env: {
|
|
26
|
+
...process.env,
|
|
27
|
+
OPENCODE_HOME: tempHome,
|
|
28
|
+
},
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
fs.mkdirSync(fakeBinDir, { recursive: true });
|
|
32
|
+
fs.writeFileSync(path.join(fakeBinDir, 'opencode'), '#!/bin/sh\nexit 0\n', 'utf8');
|
|
33
|
+
fs.chmodSync(path.join(fakeBinDir, 'opencode'), 0o755);
|
|
34
|
+
|
|
35
|
+
const result = ensureGlobalInstall({
|
|
36
|
+
projectRoot,
|
|
37
|
+
env: {
|
|
38
|
+
...process.env,
|
|
39
|
+
OPENCODE_HOME: tempHome,
|
|
40
|
+
PATH: `${fakeBinDir}${path.delimiter}${process.env.PATH ?? ''}`,
|
|
41
|
+
},
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
assert.equal(result.action, 'none');
|
|
45
|
+
assert.equal(result.installed, false);
|
|
46
|
+
assert.equal(result.doctor.status, 'healthy');
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
test('ensureGlobalInstall materializes the global install when it is missing', () => {
|
|
50
|
+
const tempHome = makeTempDir();
|
|
51
|
+
const projectRoot = makeTempDir();
|
|
52
|
+
const fakeBinDir = path.join(tempHome, 'bin');
|
|
53
|
+
|
|
54
|
+
fs.mkdirSync(fakeBinDir, { recursive: true });
|
|
55
|
+
fs.writeFileSync(path.join(fakeBinDir, 'opencode'), '#!/bin/sh\nexit 0\n', 'utf8');
|
|
56
|
+
fs.chmodSync(path.join(fakeBinDir, 'opencode'), 0o755);
|
|
57
|
+
|
|
58
|
+
const result = ensureGlobalInstall({
|
|
59
|
+
projectRoot,
|
|
60
|
+
env: {
|
|
61
|
+
...process.env,
|
|
62
|
+
OPENCODE_HOME: tempHome,
|
|
63
|
+
PATH: `${fakeBinDir}${path.delimiter}${process.env.PATH ?? ''}`,
|
|
64
|
+
},
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
assert.equal(result.action, 'installed');
|
|
68
|
+
assert.equal(result.installed, true);
|
|
69
|
+
assert.equal(result.doctor.status, 'healthy');
|
|
70
|
+
assert.equal(fs.existsSync(path.join(tempHome, 'kits', 'openkit', '.opencode', 'workflow-state.js')), true);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
test('ensureGlobalInstall returns blocked when install state is invalid', () => {
|
|
74
|
+
const tempHome = makeTempDir();
|
|
75
|
+
const projectRoot = makeTempDir();
|
|
76
|
+
const kitRoot = path.join(tempHome, 'kits', 'openkit');
|
|
77
|
+
|
|
78
|
+
writeJson(path.join(kitRoot, 'install-state.json'), {
|
|
79
|
+
schema: 'wrong-schema',
|
|
80
|
+
stateVersion: 1,
|
|
81
|
+
kit: {
|
|
82
|
+
name: 'OpenKit',
|
|
83
|
+
version: '0.1.0',
|
|
84
|
+
},
|
|
85
|
+
installation: {
|
|
86
|
+
profile: 'openkit',
|
|
87
|
+
status: 'installed',
|
|
88
|
+
installedAt: '2026-03-24T00:00:00.000Z',
|
|
89
|
+
},
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
const result = ensureGlobalInstall({
|
|
93
|
+
projectRoot,
|
|
94
|
+
env: {
|
|
95
|
+
...process.env,
|
|
96
|
+
OPENCODE_HOME: tempHome,
|
|
97
|
+
PATH: process.env.PATH ?? '',
|
|
98
|
+
},
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
assert.equal(result.action, 'blocked');
|
|
102
|
+
assert.equal(result.installed, false);
|
|
103
|
+
assert.equal(result.doctor.status, 'install-invalid');
|
|
104
|
+
assert.equal(result.doctor.recommendedCommand, 'openkit upgrade');
|
|
105
|
+
});
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import test from "node:test"
|
|
2
|
+
import assert from "node:assert/strict"
|
|
3
|
+
import fs from "node:fs"
|
|
4
|
+
import os from "node:os"
|
|
5
|
+
import path from "node:path"
|
|
6
|
+
|
|
7
|
+
import { discoverProjectShape } from "../../src/install/discovery.js"
|
|
8
|
+
|
|
9
|
+
function makeTempDir() {
|
|
10
|
+
return fs.mkdtempSync(path.join(os.tmpdir(), "openkit-discovery-"))
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function writeJson(filePath, value) {
|
|
14
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true })
|
|
15
|
+
fs.writeFileSync(filePath, `${JSON.stringify(value, null, 2)}\n`, "utf8")
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function writeText(filePath, value) {
|
|
19
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true })
|
|
20
|
+
fs.writeFileSync(filePath, value, "utf8")
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
test("discoverProjectShape classifies an OpenKit additive local metadata project", () => {
|
|
24
|
+
const projectRoot = makeTempDir()
|
|
25
|
+
|
|
26
|
+
writeJson(path.join(projectRoot, ".opencode", "opencode.json"), {
|
|
27
|
+
$schema: "https://example.com/opencode.schema.json",
|
|
28
|
+
})
|
|
29
|
+
writeJson(path.join(projectRoot, ".opencode", "install-manifest.json"), {
|
|
30
|
+
installation: {
|
|
31
|
+
mode: "additive-non-destructive",
|
|
32
|
+
},
|
|
33
|
+
})
|
|
34
|
+
writeJson(path.join(projectRoot, "registry.json"), {
|
|
35
|
+
kit: {
|
|
36
|
+
productSurface: {
|
|
37
|
+
emerging: "global-openkit-install",
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
const result = discoverProjectShape(projectRoot)
|
|
43
|
+
|
|
44
|
+
assert.equal(result.projectRoot, projectRoot)
|
|
45
|
+
assert.equal(result.classification, "openkit-additive-local-metadata")
|
|
46
|
+
assert.equal(result.hasRuntimeManifest, true)
|
|
47
|
+
assert.equal(result.hasRootInstallEntrypoint, false)
|
|
48
|
+
assert.equal(result.hasInstallManifest, true)
|
|
49
|
+
assert.equal(result.hasRegistry, true)
|
|
50
|
+
assert.deepEqual(result.notes, [])
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
test("discoverProjectShape reports an OpenCode-only runtime when only the checked-in runtime manifest exists", () => {
|
|
54
|
+
const projectRoot = makeTempDir()
|
|
55
|
+
|
|
56
|
+
writeJson(path.join(projectRoot, ".opencode", "opencode.json"), {
|
|
57
|
+
name: "runtime-only",
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
const result = discoverProjectShape(projectRoot)
|
|
61
|
+
|
|
62
|
+
assert.equal(result.classification, "opencode-runtime-only")
|
|
63
|
+
assert.equal(result.hasRuntimeManifest, true)
|
|
64
|
+
assert.equal(result.hasInstallManifest, false)
|
|
65
|
+
assert.equal(result.hasRegistry, false)
|
|
66
|
+
assert.deepEqual(result.notes, [])
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
test("discoverProjectShape flags mixed install surfaces without mutating project state", () => {
|
|
70
|
+
const projectRoot = makeTempDir()
|
|
71
|
+
|
|
72
|
+
writeJson(path.join(projectRoot, ".opencode", "opencode.json"), {
|
|
73
|
+
name: "runtime",
|
|
74
|
+
})
|
|
75
|
+
writeJson(path.join(projectRoot, "opencode.json"), {
|
|
76
|
+
install: true,
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
const before = fs.readFileSync(path.join(projectRoot, "opencode.json"), "utf8")
|
|
80
|
+
const result = discoverProjectShape(projectRoot)
|
|
81
|
+
const after = fs.readFileSync(path.join(projectRoot, "opencode.json"), "utf8")
|
|
82
|
+
|
|
83
|
+
assert.equal(result.classification, "mixed-install-surfaces")
|
|
84
|
+
assert.equal(result.hasRuntimeManifest, true)
|
|
85
|
+
assert.equal(result.hasRootInstallEntrypoint, true)
|
|
86
|
+
assert.match(result.notes[0], /both repository-local runtime and root install entrypoint/i)
|
|
87
|
+
assert.equal(after, before)
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
test("discoverProjectShape marks missing runtime surfaces as unknown", () => {
|
|
91
|
+
const projectRoot = makeTempDir()
|
|
92
|
+
|
|
93
|
+
const result = discoverProjectShape(projectRoot)
|
|
94
|
+
|
|
95
|
+
assert.equal(result.classification, "unknown")
|
|
96
|
+
assert.equal(result.hasRuntimeManifest, false)
|
|
97
|
+
assert.equal(result.hasRootInstallEntrypoint, false)
|
|
98
|
+
assert.equal(result.hasInstallManifest, false)
|
|
99
|
+
assert.equal(result.hasRegistry, false)
|
|
100
|
+
assert.match(result.notes[0], /no recognized runtime manifest/i)
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
test("discoverProjectShape reports malformed additive metadata explicitly instead of treating it as absent", () => {
|
|
104
|
+
const projectRoot = makeTempDir()
|
|
105
|
+
|
|
106
|
+
writeJson(path.join(projectRoot, ".opencode", "opencode.json"), {
|
|
107
|
+
name: "runtime-only",
|
|
108
|
+
})
|
|
109
|
+
writeText(path.join(projectRoot, ".opencode", "install-manifest.json"), "{ invalid json\n")
|
|
110
|
+
writeText(path.join(projectRoot, "registry.json"), "not-json\n")
|
|
111
|
+
|
|
112
|
+
const result = discoverProjectShape(projectRoot)
|
|
113
|
+
|
|
114
|
+
assert.equal(result.classification, "unclassified")
|
|
115
|
+
assert.equal(result.hasRuntimeManifest, true)
|
|
116
|
+
assert.equal(result.hasInstallManifest, true)
|
|
117
|
+
assert.equal(result.hasRegistry, true)
|
|
118
|
+
assert.deepEqual(result.malformedMetadata, {
|
|
119
|
+
installManifest: true,
|
|
120
|
+
registry: true,
|
|
121
|
+
})
|
|
122
|
+
assert.match(result.notes[0], /install manifest metadata is malformed/i)
|
|
123
|
+
assert.match(result.notes[1], /registry metadata is malformed/i)
|
|
124
|
+
})
|
|
@@ -0,0 +1,346 @@
|
|
|
1
|
+
import test from "node:test"
|
|
2
|
+
import assert from "node:assert/strict"
|
|
3
|
+
import fs from "node:fs"
|
|
4
|
+
import path from "node:path"
|
|
5
|
+
import { fileURLToPath } from "node:url"
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
OPENKIT_ASSET_MANIFEST,
|
|
9
|
+
listManagedAssetIds,
|
|
10
|
+
getManagedAsset,
|
|
11
|
+
listBundledAssetIds,
|
|
12
|
+
validateBundledAssetFiles,
|
|
13
|
+
} from "../../src/install/asset-manifest.js"
|
|
14
|
+
import {
|
|
15
|
+
INSTALL_STATE_SCHEMA,
|
|
16
|
+
createInstallState,
|
|
17
|
+
validateInstallState,
|
|
18
|
+
} from "../../src/install/install-state.js"
|
|
19
|
+
|
|
20
|
+
test("asset manifest defines the phase 1 managed assets", () => {
|
|
21
|
+
assert.equal(OPENKIT_ASSET_MANIFEST.schema, "openkit/asset-manifest@1")
|
|
22
|
+
assert.equal(OPENKIT_ASSET_MANIFEST.manifestVersion, 1)
|
|
23
|
+
assert.deepEqual(listManagedAssetIds(), [
|
|
24
|
+
"runtime.opencode-manifest",
|
|
25
|
+
"runtime.install-state",
|
|
26
|
+
])
|
|
27
|
+
|
|
28
|
+
const runtimeManifest = getManagedAsset("runtime.opencode-manifest")
|
|
29
|
+
assert.deepEqual(runtimeManifest, {
|
|
30
|
+
id: "runtime.opencode-manifest",
|
|
31
|
+
path: "opencode.json",
|
|
32
|
+
kind: "template",
|
|
33
|
+
templatePath: "assets/opencode.json.template",
|
|
34
|
+
phase: 1,
|
|
35
|
+
required: true,
|
|
36
|
+
adoptionAllowed: true,
|
|
37
|
+
description: "Managed install entrypoint manifest for OpenKit installs.",
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
const installStateAsset = getManagedAsset("runtime.install-state")
|
|
41
|
+
assert.deepEqual(installStateAsset, {
|
|
42
|
+
id: "runtime.install-state",
|
|
43
|
+
path: ".openkit/openkit-install.json",
|
|
44
|
+
kind: "template",
|
|
45
|
+
templatePath: "assets/openkit-install.json.template",
|
|
46
|
+
phase: 1,
|
|
47
|
+
required: true,
|
|
48
|
+
adoptionAllowed: false,
|
|
49
|
+
description: "OpenKit-managed install state persisted in the target repository.",
|
|
50
|
+
})
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
test("asset manifest defines the explicit OpenCode-native phase 1 bundle", () => {
|
|
54
|
+
assert.deepEqual(OPENKIT_ASSET_MANIFEST.bundle.namespace, "openkit")
|
|
55
|
+
assert.deepEqual(OPENKIT_ASSET_MANIFEST.bundle.phase, 1)
|
|
56
|
+
assert.deepEqual(OPENKIT_ASSET_MANIFEST.bundle.includedAssetClasses, [
|
|
57
|
+
"agents",
|
|
58
|
+
"commands",
|
|
59
|
+
"context",
|
|
60
|
+
"skills",
|
|
61
|
+
])
|
|
62
|
+
assert.deepEqual(OPENKIT_ASSET_MANIFEST.bundle.deferredAssetClasses, [
|
|
63
|
+
"plugins",
|
|
64
|
+
"package.json",
|
|
65
|
+
])
|
|
66
|
+
assert.equal(OPENKIT_ASSET_MANIFEST.bundle.assets.some((asset) => asset.id.includes("opcode")), false)
|
|
67
|
+
assert.deepEqual(OPENKIT_ASSET_MANIFEST.bundle.collisionPolicy, {
|
|
68
|
+
installNamespace: "openkit",
|
|
69
|
+
assetIdPrefix: "opencode",
|
|
70
|
+
onCollision: "fail-closed-and-require-explicit-mapping",
|
|
71
|
+
rationale: "Phase 1 ships an explicit namespaced bundle instead of overwriting unrelated OpenCode-native assets.",
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
assert.deepEqual(listBundledAssetIds(), [
|
|
75
|
+
"opencode.bundle.readme",
|
|
76
|
+
"opencode.agent.ArchitectAgent",
|
|
77
|
+
"opencode.agent.BAAgent",
|
|
78
|
+
"opencode.agent.CodeReviewer",
|
|
79
|
+
"opencode.agent.FullstackAgent",
|
|
80
|
+
"opencode.agent.MasterOrchestrator",
|
|
81
|
+
"opencode.agent.PMAgent",
|
|
82
|
+
"opencode.agent.QAAgent",
|
|
83
|
+
"opencode.agent.TechLeadAgent",
|
|
84
|
+
"opencode.command.brainstorm",
|
|
85
|
+
"opencode.command.delivery",
|
|
86
|
+
"opencode.command.execute-plan",
|
|
87
|
+
"opencode.command.migrate",
|
|
88
|
+
"opencode.command.quick-task",
|
|
89
|
+
"opencode.command.task",
|
|
90
|
+
"opencode.command.write-plan",
|
|
91
|
+
"opencode.context.lane-selection",
|
|
92
|
+
"opencode.skill.brainstorming",
|
|
93
|
+
"opencode.skill.code-review",
|
|
94
|
+
"opencode.skill.subagent-driven-development",
|
|
95
|
+
"opencode.skill.systematic-debugging",
|
|
96
|
+
"opencode.skill.test-driven-development",
|
|
97
|
+
"opencode.skill.using-skills",
|
|
98
|
+
"opencode.skill.verification-before-completion",
|
|
99
|
+
"opencode.skill.writing-plans",
|
|
100
|
+
"opencode.skill.writing-specs",
|
|
101
|
+
])
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
test("install state factory records managed assets, adopted assets, warnings, and conflicts", () => {
|
|
105
|
+
const state = createInstallState({
|
|
106
|
+
kitVersion: "0.2.0",
|
|
107
|
+
profile: "openkit-core",
|
|
108
|
+
managedAssets: [
|
|
109
|
+
{
|
|
110
|
+
assetId: "runtime.install-state",
|
|
111
|
+
path: ".openkit/openkit-install.json",
|
|
112
|
+
status: "managed",
|
|
113
|
+
checksum: "sha256:install-state",
|
|
114
|
+
},
|
|
115
|
+
],
|
|
116
|
+
adoptedAssets: [
|
|
117
|
+
{
|
|
118
|
+
assetId: "runtime.opencode-manifest",
|
|
119
|
+
path: "opencode.json",
|
|
120
|
+
adoptedFrom: "user-existing",
|
|
121
|
+
status: "adopted",
|
|
122
|
+
},
|
|
123
|
+
],
|
|
124
|
+
warnings: [
|
|
125
|
+
{
|
|
126
|
+
code: "root-manifest-adopted",
|
|
127
|
+
message: "Existing root opencode.json was adopted instead of overwritten.",
|
|
128
|
+
},
|
|
129
|
+
],
|
|
130
|
+
conflicts: [
|
|
131
|
+
{
|
|
132
|
+
assetId: "runtime.opencode-manifest",
|
|
133
|
+
path: "opencode.json",
|
|
134
|
+
reason: "content-mismatch",
|
|
135
|
+
resolution: "manual-review-required",
|
|
136
|
+
},
|
|
137
|
+
],
|
|
138
|
+
})
|
|
139
|
+
|
|
140
|
+
assert.equal(state.schema, INSTALL_STATE_SCHEMA)
|
|
141
|
+
assert.equal(state.stateVersion, 1)
|
|
142
|
+
assert.equal(state.kit.name, "OpenKit")
|
|
143
|
+
assert.equal(state.kit.version, "0.2.0")
|
|
144
|
+
assert.equal(state.installation.profile, "openkit-core")
|
|
145
|
+
assert.equal(state.installation.status, "installed")
|
|
146
|
+
assert.match(state.installation.installedAt, /^\d{4}-\d{2}-\d{2}T/)
|
|
147
|
+
assert.deepEqual(state.assets.managed, [
|
|
148
|
+
{
|
|
149
|
+
assetId: "runtime.install-state",
|
|
150
|
+
path: ".openkit/openkit-install.json",
|
|
151
|
+
status: "managed",
|
|
152
|
+
checksum: "sha256:install-state",
|
|
153
|
+
},
|
|
154
|
+
])
|
|
155
|
+
assert.deepEqual(state.assets.adopted, [
|
|
156
|
+
{
|
|
157
|
+
assetId: "runtime.opencode-manifest",
|
|
158
|
+
path: "opencode.json",
|
|
159
|
+
adoptedFrom: "user-existing",
|
|
160
|
+
status: "adopted",
|
|
161
|
+
},
|
|
162
|
+
])
|
|
163
|
+
assert.deepEqual(state.warnings, [
|
|
164
|
+
{
|
|
165
|
+
code: "root-manifest-adopted",
|
|
166
|
+
message: "Existing root opencode.json was adopted instead of overwritten.",
|
|
167
|
+
},
|
|
168
|
+
])
|
|
169
|
+
assert.deepEqual(state.conflicts, [
|
|
170
|
+
{
|
|
171
|
+
assetId: "runtime.opencode-manifest",
|
|
172
|
+
path: "opencode.json",
|
|
173
|
+
reason: "content-mismatch",
|
|
174
|
+
resolution: "manual-review-required",
|
|
175
|
+
},
|
|
176
|
+
])
|
|
177
|
+
})
|
|
178
|
+
|
|
179
|
+
test("install state validation rejects malformed state objects", () => {
|
|
180
|
+
assert.deepEqual(validateInstallState(createInstallState({})), [])
|
|
181
|
+
|
|
182
|
+
const errors = validateInstallState({
|
|
183
|
+
schema: "openkit/install-state@999",
|
|
184
|
+
stateVersion: 2,
|
|
185
|
+
kit: { name: "", version: "" },
|
|
186
|
+
installation: {
|
|
187
|
+
profile: "",
|
|
188
|
+
status: "pending",
|
|
189
|
+
installedAt: "not-a-timestamp",
|
|
190
|
+
},
|
|
191
|
+
assets: {
|
|
192
|
+
managed: [
|
|
193
|
+
{
|
|
194
|
+
assetId: "runtime.install-state",
|
|
195
|
+
path: "",
|
|
196
|
+
status: "unsupported",
|
|
197
|
+
},
|
|
198
|
+
],
|
|
199
|
+
adopted: [
|
|
200
|
+
{
|
|
201
|
+
assetId: "runtime.opencode-manifest",
|
|
202
|
+
path: "opencode.json",
|
|
203
|
+
status: "managed",
|
|
204
|
+
},
|
|
205
|
+
],
|
|
206
|
+
},
|
|
207
|
+
warnings: [
|
|
208
|
+
{
|
|
209
|
+
code: "",
|
|
210
|
+
message: "warning without code",
|
|
211
|
+
},
|
|
212
|
+
],
|
|
213
|
+
conflicts: [
|
|
214
|
+
{
|
|
215
|
+
assetId: "runtime.opencode-manifest",
|
|
216
|
+
path: "opencode.json",
|
|
217
|
+
reason: "",
|
|
218
|
+
resolution: "manual-review-required",
|
|
219
|
+
},
|
|
220
|
+
],
|
|
221
|
+
})
|
|
222
|
+
|
|
223
|
+
assert.deepEqual(errors, [
|
|
224
|
+
"schema must be 'openkit/install-state@1'",
|
|
225
|
+
"stateVersion must be 1",
|
|
226
|
+
"kit.name must be a non-empty string",
|
|
227
|
+
"kit.version must be a non-empty string",
|
|
228
|
+
"installation.profile must be a non-empty string",
|
|
229
|
+
"installation.status must be 'installed'",
|
|
230
|
+
"installation.installedAt must be an ISO-8601 timestamp",
|
|
231
|
+
"assets.managed[0].path must be a non-empty string",
|
|
232
|
+
"assets.managed[0].status must be one of: managed, materialized",
|
|
233
|
+
"assets.adopted[0].status must be 'adopted'",
|
|
234
|
+
"warnings[0].code must be a non-empty string",
|
|
235
|
+
"conflicts[0].reason must be a non-empty string",
|
|
236
|
+
])
|
|
237
|
+
})
|
|
238
|
+
|
|
239
|
+
test("install state validation rejects wrong collection types", () => {
|
|
240
|
+
const errors = validateInstallState({
|
|
241
|
+
schema: INSTALL_STATE_SCHEMA,
|
|
242
|
+
stateVersion: 1,
|
|
243
|
+
kit: { name: "OpenKit", version: "0.2.0" },
|
|
244
|
+
installation: {
|
|
245
|
+
profile: "openkit-core",
|
|
246
|
+
status: "installed",
|
|
247
|
+
installedAt: "2026-03-22T12:00:00.000Z",
|
|
248
|
+
},
|
|
249
|
+
assets: {
|
|
250
|
+
managed: { assetId: "runtime.install-state" },
|
|
251
|
+
adopted: "not-an-array",
|
|
252
|
+
},
|
|
253
|
+
warnings: { code: "warning" },
|
|
254
|
+
conflicts: null,
|
|
255
|
+
})
|
|
256
|
+
|
|
257
|
+
assert.deepEqual(errors, [
|
|
258
|
+
"assets.managed must be an array",
|
|
259
|
+
"assets.adopted must be an array",
|
|
260
|
+
"warnings must be an array",
|
|
261
|
+
"conflicts must be an array",
|
|
262
|
+
])
|
|
263
|
+
})
|
|
264
|
+
|
|
265
|
+
test("install state validation rejects parseable non-ISO timestamps", () => {
|
|
266
|
+
const errors = validateInstallState({
|
|
267
|
+
schema: INSTALL_STATE_SCHEMA,
|
|
268
|
+
stateVersion: 1,
|
|
269
|
+
kit: { name: "OpenKit", version: "0.2.0" },
|
|
270
|
+
installation: {
|
|
271
|
+
profile: "openkit-core",
|
|
272
|
+
status: "installed",
|
|
273
|
+
installedAt: "March 22, 2026",
|
|
274
|
+
},
|
|
275
|
+
assets: {
|
|
276
|
+
managed: [],
|
|
277
|
+
adopted: [],
|
|
278
|
+
},
|
|
279
|
+
warnings: [],
|
|
280
|
+
conflicts: [],
|
|
281
|
+
})
|
|
282
|
+
|
|
283
|
+
assert.deepEqual(errors, [
|
|
284
|
+
"installation.installedAt must be an ISO-8601 timestamp",
|
|
285
|
+
])
|
|
286
|
+
})
|
|
287
|
+
|
|
288
|
+
test("asset templates exist and point at the new schemas", () => {
|
|
289
|
+
const testDir = path.dirname(fileURLToPath(import.meta.url))
|
|
290
|
+
const opencodeTemplatePath = path.resolve(testDir, "../../assets/opencode.json.template")
|
|
291
|
+
const installTemplatePath = path.resolve(testDir, "../../assets/openkit-install.json.template")
|
|
292
|
+
|
|
293
|
+
const opencodeTemplate = JSON.parse(fs.readFileSync(opencodeTemplatePath, "utf8"))
|
|
294
|
+
const installTemplate = JSON.parse(fs.readFileSync(installTemplatePath, "utf8"))
|
|
295
|
+
|
|
296
|
+
assert.equal(opencodeTemplate.installState.path, ".openkit/openkit-install.json")
|
|
297
|
+
assert.equal(opencodeTemplate.installState.schema, INSTALL_STATE_SCHEMA)
|
|
298
|
+
assert.equal(opencodeTemplate.productSurface.installReadiness, "managed")
|
|
299
|
+
assert.equal(opencodeTemplate.productSurface.installationMode, "openkit-managed")
|
|
300
|
+
|
|
301
|
+
assert.equal(installTemplate.schema, INSTALL_STATE_SCHEMA)
|
|
302
|
+
assert.equal(installTemplate.stateVersion, 1)
|
|
303
|
+
assert.equal(installTemplate.installation.profile, "openkit-core")
|
|
304
|
+
assert.deepEqual(installTemplate.assets, {
|
|
305
|
+
managed: [],
|
|
306
|
+
adopted: [],
|
|
307
|
+
})
|
|
308
|
+
assert.deepEqual(installTemplate.warnings, [])
|
|
309
|
+
assert.deepEqual(installTemplate.conflicts, [])
|
|
310
|
+
})
|
|
311
|
+
|
|
312
|
+
test("bundled asset manifest matches the derived asset bundle on disk", () => {
|
|
313
|
+
const testDir = path.dirname(fileURLToPath(import.meta.url))
|
|
314
|
+
const projectRoot = path.resolve(testDir, "../..")
|
|
315
|
+
|
|
316
|
+
const validation = validateBundledAssetFiles(projectRoot)
|
|
317
|
+
|
|
318
|
+
assert.deepEqual(validation.missingFiles, [])
|
|
319
|
+
assert.deepEqual(validation.mismatchedFiles, [])
|
|
320
|
+
assert.equal(validation.bundleFileCount, 26)
|
|
321
|
+
assert.deepEqual(validation.extraBundledFiles, [])
|
|
322
|
+
})
|
|
323
|
+
|
|
324
|
+
test("bundled asset validator reports drift between source and derived copy", () => {
|
|
325
|
+
const testDir = path.dirname(fileURLToPath(import.meta.url))
|
|
326
|
+
const projectRoot = path.resolve(testDir, "../..")
|
|
327
|
+
const bundledFilePath = path.join(projectRoot, "assets/install-bundle/opencode/commands/task.md")
|
|
328
|
+
const originalContents = fs.readFileSync(bundledFilePath, "utf8")
|
|
329
|
+
|
|
330
|
+
fs.writeFileSync(bundledFilePath, `${originalContents}\n<!-- drift -->\n`, "utf8")
|
|
331
|
+
|
|
332
|
+
try {
|
|
333
|
+
const validation = validateBundledAssetFiles(projectRoot)
|
|
334
|
+
|
|
335
|
+
assert.deepEqual(validation.missingFiles, [])
|
|
336
|
+
assert.deepEqual(validation.mismatchedFiles, [
|
|
337
|
+
{
|
|
338
|
+
id: "opencode.command.task",
|
|
339
|
+
sourcePath: "commands/task.md",
|
|
340
|
+
bundledPath: "assets/install-bundle/opencode/commands/task.md",
|
|
341
|
+
},
|
|
342
|
+
])
|
|
343
|
+
} finally {
|
|
344
|
+
fs.writeFileSync(bundledFilePath, originalContents, "utf8")
|
|
345
|
+
}
|
|
346
|
+
})
|