@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.
Files changed (178) hide show
  1. package/.opencode/README.md +47 -0
  2. package/.opencode/install-manifest.json +41 -0
  3. package/.opencode/lib/artifact-scaffolder.js +111 -0
  4. package/.opencode/lib/contract-consistency.js +218 -0
  5. package/.opencode/lib/parallel-execution-rules.js +261 -0
  6. package/.opencode/lib/runtime-paths.js +95 -0
  7. package/.opencode/lib/runtime-summary.js +82 -0
  8. package/.opencode/lib/state-guard.js +99 -0
  9. package/.opencode/lib/task-board-rules.js +375 -0
  10. package/.opencode/lib/work-item-store.js +280 -0
  11. package/.opencode/lib/workflow-state-controller.js +1739 -0
  12. package/.opencode/lib/workflow-state-rules.js +331 -0
  13. package/.opencode/opencode.json +93 -0
  14. package/.opencode/package.json +3 -0
  15. package/.opencode/tests/artifact-scaffolder.test.js +733 -0
  16. package/.opencode/tests/multi-work-item-runtime.test.js +369 -0
  17. package/.opencode/tests/parallel-execution-runtime.test.js +259 -0
  18. package/.opencode/tests/session-start-hook.test.js +357 -0
  19. package/.opencode/tests/state-guard.test.js +124 -0
  20. package/.opencode/tests/task-board-rules.test.js +204 -0
  21. package/.opencode/tests/work-item-store.test.js +380 -0
  22. package/.opencode/tests/workflow-behavior.test.js +149 -0
  23. package/.opencode/tests/workflow-contract-consistency.test.js +387 -0
  24. package/.opencode/tests/workflow-state-cli.test.js +1275 -0
  25. package/.opencode/tests/workflow-state-controller.test.js +1038 -0
  26. package/.opencode/work-items/feature-001/state.json +70 -0
  27. package/.opencode/work-items/index.json +13 -0
  28. package/.opencode/workflow-state.js +489 -0
  29. package/.opencode/workflow-state.json +70 -0
  30. package/AGENTS.md +265 -0
  31. package/README.md +401 -0
  32. package/agents/architect-agent.md +63 -0
  33. package/agents/ba-agent.md +56 -0
  34. package/agents/code-reviewer.md +77 -0
  35. package/agents/fullstack-agent.md +115 -0
  36. package/agents/master-orchestrator.md +60 -0
  37. package/agents/pm-agent.md +56 -0
  38. package/agents/qa-agent.md +124 -0
  39. package/agents/tech-lead-agent.md +60 -0
  40. package/assets/install-bundle/README.md +7 -0
  41. package/assets/install-bundle/opencode/README.md +11 -0
  42. package/assets/install-bundle/opencode/agents/ArchitectAgent.md +63 -0
  43. package/assets/install-bundle/opencode/agents/BAAgent.md +56 -0
  44. package/assets/install-bundle/opencode/agents/CodeReviewer.md +77 -0
  45. package/assets/install-bundle/opencode/agents/FullstackAgent.md +115 -0
  46. package/assets/install-bundle/opencode/agents/MasterOrchestrator.md +60 -0
  47. package/assets/install-bundle/opencode/agents/PMAgent.md +56 -0
  48. package/assets/install-bundle/opencode/agents/QAAgent.md +124 -0
  49. package/assets/install-bundle/opencode/agents/TechLeadAgent.md +60 -0
  50. package/assets/install-bundle/opencode/commands/brainstorm.md +44 -0
  51. package/assets/install-bundle/opencode/commands/delivery.md +45 -0
  52. package/assets/install-bundle/opencode/commands/execute-plan.md +44 -0
  53. package/assets/install-bundle/opencode/commands/migrate.md +61 -0
  54. package/assets/install-bundle/opencode/commands/quick-task.md +45 -0
  55. package/assets/install-bundle/opencode/commands/task.md +46 -0
  56. package/assets/install-bundle/opencode/commands/write-plan.md +50 -0
  57. package/assets/install-bundle/opencode/context/core/lane-selection.md +54 -0
  58. package/assets/install-bundle/opencode/skills/brainstorming/SKILL.md +51 -0
  59. package/assets/install-bundle/opencode/skills/code-review/SKILL.md +48 -0
  60. package/assets/install-bundle/opencode/skills/subagent-driven-development/SKILL.md +79 -0
  61. package/assets/install-bundle/opencode/skills/systematic-debugging/SKILL.md +61 -0
  62. package/assets/install-bundle/opencode/skills/test-driven-development/SKILL.md +48 -0
  63. package/assets/install-bundle/opencode/skills/using-skills/SKILL.md +39 -0
  64. package/assets/install-bundle/opencode/skills/verification-before-completion/SKILL.md +137 -0
  65. package/assets/install-bundle/opencode/skills/writing-plans/SKILL.md +68 -0
  66. package/assets/install-bundle/opencode/skills/writing-specs/SKILL.md +47 -0
  67. package/assets/opencode.json.template +11 -0
  68. package/assets/openkit-install.json.template +19 -0
  69. package/bin/openkit.js +9 -0
  70. package/commands/brainstorm.md +44 -0
  71. package/commands/delivery.md +45 -0
  72. package/commands/execute-plan.md +44 -0
  73. package/commands/migrate.md +61 -0
  74. package/commands/quick-task.md +45 -0
  75. package/commands/task.md +46 -0
  76. package/commands/write-plan.md +50 -0
  77. package/context/core/approval-gates.md +146 -0
  78. package/context/core/code-quality.md +42 -0
  79. package/context/core/issue-routing.md +85 -0
  80. package/context/core/lane-selection.md +54 -0
  81. package/context/core/project-config.md +143 -0
  82. package/context/core/session-resume.md +85 -0
  83. package/context/core/workflow-state-schema.md +224 -0
  84. package/context/core/workflow.md +442 -0
  85. package/context/navigation.md +94 -0
  86. package/docs/adr/README.md +6 -0
  87. package/docs/architecture/2026-03-20-task-intake-dashboard.md +54 -0
  88. package/docs/architecture/README.md +7 -0
  89. package/docs/briefs/2026-03-20-task-intake-dashboard.md +48 -0
  90. package/docs/briefs/README.md +7 -0
  91. package/docs/governance/README.md +25 -0
  92. package/docs/governance/adr-policy.md +27 -0
  93. package/docs/governance/definition-of-done.md +17 -0
  94. package/docs/governance/naming-conventions.md +21 -0
  95. package/docs/governance/severity-levels.md +12 -0
  96. package/docs/maintainer/README.md +51 -0
  97. package/docs/operations/README.md +79 -0
  98. package/docs/operations/internal-records/2026-03-24-release-checklist.md +79 -0
  99. package/docs/operations/internal-records/2026-03-24-simplified-install-ux.md +36 -0
  100. package/docs/operations/internal-records/README.md +18 -0
  101. package/docs/operations/runbooks/README.md +23 -0
  102. package/docs/operations/runbooks/openkit-daily-usage.md +288 -0
  103. package/docs/operations/runbooks/workflow-state-smoke-tests.md +302 -0
  104. package/docs/operator/README.md +50 -0
  105. package/docs/plans/2026-03-20-task-intake-dashboard.md +49 -0
  106. package/docs/plans/2026-03-21-openkit-full-delivery-multi-task-runtime.md +521 -0
  107. package/docs/plans/2026-03-23-openkit-global-install-runtime.md +157 -0
  108. package/docs/plans/README.md +7 -0
  109. package/docs/qa/2026-03-20-task-intake-dashboard.md +41 -0
  110. package/docs/qa/README.md +7 -0
  111. package/docs/specs/2026-03-20-task-intake-dashboard.md +50 -0
  112. package/docs/specs/2026-03-21-openkit-full-delivery-multi-task-runtime.md +462 -0
  113. package/docs/specs/README.md +7 -0
  114. package/docs/templates/README.md +36 -0
  115. package/docs/templates/adr-template.md +18 -0
  116. package/docs/templates/architecture-template.md +31 -0
  117. package/docs/templates/implementation-plan-template.md +32 -0
  118. package/docs/templates/migration-baseline-checklist.md +48 -0
  119. package/docs/templates/migration-plan-template.md +52 -0
  120. package/docs/templates/migration-report-template.md +74 -0
  121. package/docs/templates/migration-verify-checklist.md +39 -0
  122. package/docs/templates/product-brief-template.md +32 -0
  123. package/docs/templates/qa-report-template.md +37 -0
  124. package/docs/templates/quick-task-template.md +36 -0
  125. package/docs/templates/spec-template.md +31 -0
  126. package/hooks/hooks.json +16 -0
  127. package/hooks/session-start +162 -0
  128. package/package.json +24 -0
  129. package/registry.json +328 -0
  130. package/skills/brainstorming/SKILL.md +51 -0
  131. package/skills/code-review/SKILL.md +48 -0
  132. package/skills/subagent-driven-development/SKILL.md +79 -0
  133. package/skills/systematic-debugging/SKILL.md +61 -0
  134. package/skills/test-driven-development/SKILL.md +48 -0
  135. package/skills/using-skills/SKILL.md +39 -0
  136. package/skills/verification-before-completion/SKILL.md +137 -0
  137. package/skills/writing-plans/SKILL.md +68 -0
  138. package/skills/writing-specs/SKILL.md +47 -0
  139. package/src/audit/vietnamese-detection.js +259 -0
  140. package/src/cli/commands/detect-vietnamese.js +24 -0
  141. package/src/cli/commands/doctor.js +33 -0
  142. package/src/cli/commands/help.js +33 -0
  143. package/src/cli/commands/init.js +25 -0
  144. package/src/cli/commands/install-global.js +26 -0
  145. package/src/cli/commands/install.js +25 -0
  146. package/src/cli/commands/run.js +63 -0
  147. package/src/cli/commands/uninstall.js +32 -0
  148. package/src/cli/commands/upgrade.js +25 -0
  149. package/src/cli/conflict-output.js +19 -0
  150. package/src/cli/index.js +56 -0
  151. package/src/global/doctor.js +101 -0
  152. package/src/global/ensure-install.js +32 -0
  153. package/src/global/install-state.js +73 -0
  154. package/src/global/launcher.js +51 -0
  155. package/src/global/materialize.js +123 -0
  156. package/src/global/paths.js +85 -0
  157. package/src/global/uninstall.js +25 -0
  158. package/src/global/workspace-state.js +63 -0
  159. package/src/install/asset-manifest.js +284 -0
  160. package/src/install/conflicts.js +43 -0
  161. package/src/install/discovery.js +138 -0
  162. package/src/install/install-state.js +136 -0
  163. package/src/install/materialize.js +158 -0
  164. package/src/install/merge-policy.js +145 -0
  165. package/src/runtime/doctor.js +281 -0
  166. package/src/runtime/launcher.js +49 -0
  167. package/src/runtime/opencode-layering.js +135 -0
  168. package/src/runtime/openkit-managed-summary.js +27 -0
  169. package/tests/cli/openkit-cli.test.js +417 -0
  170. package/tests/global/doctor.test.js +130 -0
  171. package/tests/global/ensure-install.test.js +105 -0
  172. package/tests/install/discovery.test.js +124 -0
  173. package/tests/install/install-state.test.js +346 -0
  174. package/tests/install/materialize.test.js +244 -0
  175. package/tests/install/merge-policy.test.js +177 -0
  176. package/tests/runtime/doctor.test.js +430 -0
  177. package/tests/runtime/launcher.test.js +230 -0
  178. package/tests/runtime/module-boundary.test.js +16 -0
@@ -0,0 +1,244 @@
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 { validateInstallState } from "../../src/install/install-state.js"
8
+ import { materializeInstall } from "../../src/install/materialize.js"
9
+
10
+ function makeTempDir() {
11
+ return fs.mkdtempSync(path.join(os.tmpdir(), "openkit-materialize-"))
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
+ function readJson(filePath) {
20
+ return JSON.parse(fs.readFileSync(filePath, "utf8"))
21
+ }
22
+
23
+ test("materializeInstall creates managed install files without mutating the checked-in runtime surface", () => {
24
+ const projectRoot = makeTempDir()
25
+ const runtimeManifestPath = path.join(projectRoot, ".opencode", "opencode.json")
26
+
27
+ writeJson(runtimeManifestPath, {
28
+ $schema: "https://example.com/opencode.schema.json",
29
+ runtime: true,
30
+ })
31
+
32
+ const beforeRuntimeManifest = fs.readFileSync(runtimeManifestPath, "utf8")
33
+ const result = materializeInstall(projectRoot, {
34
+ kitVersion: "0.2.0",
35
+ now: new Date("2026-03-22T12:00:00.000Z"),
36
+ })
37
+
38
+ const rootManifestPath = path.join(projectRoot, "opencode.json")
39
+ const installStatePath = path.join(projectRoot, ".openkit", "openkit-install.json")
40
+ const rootManifest = readJson(rootManifestPath)
41
+ const installState = readJson(installStatePath)
42
+
43
+ assert.equal(result.conflicts.length, 0)
44
+ assert.equal(result.warnings.length, 0)
45
+ assert.deepEqual(rootManifest, {
46
+ installState: {
47
+ path: ".openkit/openkit-install.json",
48
+ schema: "openkit/install-state@1",
49
+ },
50
+ productSurface: {
51
+ current: "global-openkit-install",
52
+ installReadiness: "managed",
53
+ installationMode: "openkit-managed",
54
+ },
55
+ })
56
+ assert.equal(fs.readFileSync(runtimeManifestPath, "utf8"), beforeRuntimeManifest)
57
+ assert.deepEqual(validateInstallState(installState), [])
58
+ assert.equal(installState.kit.version, "0.2.0")
59
+ assert.equal(installState.installation.installedAt, "2026-03-22T12:00:00.000Z")
60
+ assert.deepEqual(installState.assets.managed, [
61
+ {
62
+ assetId: "runtime.opencode-manifest",
63
+ path: "opencode.json",
64
+ status: "materialized",
65
+ },
66
+ {
67
+ assetId: "runtime.install-state",
68
+ path: ".openkit/openkit-install.json",
69
+ status: "managed",
70
+ },
71
+ ])
72
+ })
73
+
74
+ test("materializeInstall additively inserts allowed managed-install keys into an existing root manifest", () => {
75
+ const projectRoot = makeTempDir()
76
+ const rootManifestPath = path.join(projectRoot, "opencode.json")
77
+
78
+ writeJson(path.join(projectRoot, ".opencode", "opencode.json"), {
79
+ runtime: true,
80
+ })
81
+ writeJson(rootManifestPath, {
82
+ plugin: ["existing-plugin"],
83
+ instructions: ["LOCAL.md"],
84
+ theme: "light",
85
+ })
86
+
87
+ const result = materializeInstall(projectRoot, {
88
+ now: new Date("2026-03-22T12:00:00.000Z"),
89
+ })
90
+ const afterRootManifest = readJson(rootManifestPath)
91
+ const installState = readJson(path.join(projectRoot, ".openkit", "openkit-install.json"))
92
+
93
+ assert.deepEqual(afterRootManifest, {
94
+ plugin: ["existing-plugin"],
95
+ instructions: ["LOCAL.md"],
96
+ theme: "light",
97
+ installState: {
98
+ path: ".openkit/openkit-install.json",
99
+ schema: "openkit/install-state@1",
100
+ },
101
+ productSurface: {
102
+ current: "global-openkit-install",
103
+ installReadiness: "managed",
104
+ installationMode: "openkit-managed",
105
+ },
106
+ })
107
+ assert.deepEqual(result.conflicts, [])
108
+ assert.deepEqual(result.managedAssets, [
109
+ {
110
+ assetId: "runtime.opencode-manifest",
111
+ path: "opencode.json",
112
+ status: "materialized",
113
+ },
114
+ {
115
+ assetId: "runtime.install-state",
116
+ path: ".openkit/openkit-install.json",
117
+ status: "managed",
118
+ },
119
+ ])
120
+ assert.deepEqual(validateInstallState(installState), [])
121
+ assert.deepEqual(installState.assets.adopted, [])
122
+ assert.deepEqual(installState.conflicts, result.conflicts)
123
+ })
124
+
125
+ test("materializeInstall fails closed for unsupported root manifest rewrites", () => {
126
+ const projectRoot = makeTempDir()
127
+ const rootManifestPath = path.join(projectRoot, "opencode.json")
128
+
129
+ writeJson(path.join(projectRoot, ".opencode", "opencode.json"), {
130
+ runtime: true,
131
+ })
132
+ writeJson(rootManifestPath, {
133
+ theme: "light",
134
+ productSurface: {
135
+ current: "custom-surface",
136
+ },
137
+ })
138
+
139
+ const beforeRootManifest = fs.readFileSync(rootManifestPath, "utf8")
140
+ const result = materializeInstall(projectRoot, {
141
+ now: new Date("2026-03-22T12:00:00.000Z"),
142
+ })
143
+ const afterRootManifest = readJson(rootManifestPath)
144
+ const installStatePath = path.join(projectRoot, ".openkit", "openkit-install.json")
145
+
146
+ assert.deepEqual(afterRootManifest, JSON.parse(beforeRootManifest))
147
+ assert.deepEqual(result.conflicts, [
148
+ {
149
+ assetId: "runtime.opencode-manifest",
150
+ path: "opencode.json",
151
+ field: "productSurface",
152
+ reason: "unsupported-top-level-key",
153
+ currentValue: {
154
+ current: "custom-surface",
155
+ },
156
+ desiredValue: {
157
+ current: "global-openkit-install",
158
+ installReadiness: "managed",
159
+ installationMode: "openkit-managed",
160
+ },
161
+ resolution: "manual-review-required",
162
+ },
163
+ ])
164
+ assert.equal(fs.existsSync(installStatePath), false)
165
+ assert.deepEqual(result.managedAssets, [])
166
+ assert.deepEqual(result.adoptedAssets, [
167
+ {
168
+ assetId: "runtime.opencode-manifest",
169
+ path: "opencode.json",
170
+ adoptedFrom: "user-existing",
171
+ status: "adopted",
172
+ },
173
+ ])
174
+ })
175
+
176
+ test("materializeInstall reports pre-existing user-owned install-state without mutating managed install assets", () => {
177
+ const projectRoot = makeTempDir()
178
+ const rootManifestPath = path.join(projectRoot, "opencode.json")
179
+ const installStatePath = path.join(projectRoot, ".openkit", "openkit-install.json")
180
+
181
+ writeJson(rootManifestPath, {
182
+ plugin: ["existing-plugin"],
183
+ })
184
+ writeJson(installStatePath, {
185
+ schema: "custom/install-state",
186
+ owner: "user",
187
+ })
188
+
189
+ const beforeRootManifest = fs.readFileSync(rootManifestPath, "utf8")
190
+ const beforeInstallState = fs.readFileSync(installStatePath, "utf8")
191
+ const result = materializeInstall(projectRoot, {
192
+ now: new Date("2026-03-22T12:00:00.000Z"),
193
+ })
194
+
195
+ assert.equal(result.conflicts.length, 1)
196
+ assert.deepEqual(result.conflicts, [
197
+ {
198
+ assetId: "runtime.install-state",
199
+ path: ".openkit/openkit-install.json",
200
+ reason: "existing-managed-asset",
201
+ resolution: "manual-review-required",
202
+ },
203
+ ])
204
+ assert.equal(fs.readFileSync(rootManifestPath, "utf8"), beforeRootManifest)
205
+ assert.equal(fs.readFileSync(installStatePath, "utf8"), beforeInstallState)
206
+ })
207
+
208
+ test("materializeInstall does not leave a dangling root manifest when install-state write fails", () => {
209
+ const projectRoot = makeTempDir()
210
+ const runtimeManifestPath = path.join(projectRoot, ".opencode", "opencode.json")
211
+ const rootManifestPath = path.join(projectRoot, "opencode.json")
212
+ const openkitDirPath = path.join(projectRoot, ".openkit")
213
+ const originalWriteFileSync = fs.writeFileSync
214
+
215
+ writeJson(runtimeManifestPath, {
216
+ runtime: true,
217
+ })
218
+
219
+ fs.mkdirSync(openkitDirPath, { recursive: true })
220
+
221
+ try {
222
+ fs.writeFileSync = (filePath, data, options) => {
223
+ if (filePath === path.join(projectRoot, ".openkit", "openkit-install.json")) {
224
+ throw new Error("simulated install-state write failure")
225
+ }
226
+
227
+ return originalWriteFileSync(filePath, data, options)
228
+ }
229
+
230
+ assert.throws(
231
+ () => {
232
+ materializeInstall(projectRoot, {
233
+ now: new Date("2026-03-22T12:00:00.000Z"),
234
+ })
235
+ },
236
+ /simulated install-state write failure/,
237
+ )
238
+ } finally {
239
+ fs.writeFileSync = originalWriteFileSync
240
+ }
241
+
242
+ assert.equal(fs.existsSync(rootManifestPath), false)
243
+ assert.equal(fs.existsSync(path.join(projectRoot, ".openkit", "openkit-install.json")), false)
244
+ })
@@ -0,0 +1,177 @@
1
+ import test from "node:test"
2
+ import assert from "node:assert/strict"
3
+
4
+ import { applyOpenKitMergePolicy } from "../../src/install/merge-policy.js"
5
+
6
+ test("applyOpenKitMergePolicy additively merges supported plugin and instructions fields", () => {
7
+ const currentConfig = {
8
+ $schema: "https://example.com/schema.json",
9
+ plugin: ["existing-plugin"],
10
+ instructions: ["local-note.md"],
11
+ }
12
+
13
+ const desiredConfig = {
14
+ plugin: ["existing-plugin", "openkit-plugin"],
15
+ instructions: ["local-note.md", "OPENKIT.md"],
16
+ }
17
+
18
+ const result = applyOpenKitMergePolicy({ currentConfig, desiredConfig })
19
+
20
+ assert.deepEqual(result.config, {
21
+ $schema: "https://example.com/schema.json",
22
+ plugin: ["existing-plugin", "openkit-plugin"],
23
+ instructions: ["local-note.md", "OPENKIT.md"],
24
+ })
25
+ assert.deepEqual(result.conflicts, [])
26
+ assert.deepEqual(result.appliedFields, ["plugin", "instructions"])
27
+ })
28
+
29
+ test("applyOpenKitMergePolicy rejects unsupported top-level rewrites", () => {
30
+ const result = applyOpenKitMergePolicy({
31
+ currentConfig: {
32
+ plugin: ["existing-plugin"],
33
+ theme: "light",
34
+ },
35
+ desiredConfig: {
36
+ plugin: ["existing-plugin", "openkit-plugin"],
37
+ theme: "dark",
38
+ },
39
+ })
40
+
41
+ assert.deepEqual(result.config, {
42
+ plugin: ["existing-plugin", "openkit-plugin"],
43
+ theme: "light",
44
+ })
45
+ assert.equal(result.conflicts.length, 1)
46
+ assert.deepEqual(result.conflicts[0], {
47
+ field: "theme",
48
+ reason: "unsupported-top-level-key",
49
+ currentValue: "light",
50
+ desiredValue: "dark",
51
+ })
52
+ })
53
+
54
+ test("applyOpenKitMergePolicy rejects unclassified key additions", () => {
55
+ const result = applyOpenKitMergePolicy({
56
+ currentConfig: {
57
+ plugin: [],
58
+ },
59
+ desiredConfig: {
60
+ plugin: ["openkit-plugin"],
61
+ wrappers: {
62
+ managed: true,
63
+ },
64
+ },
65
+ })
66
+
67
+ assert.deepEqual(result.config, {
68
+ plugin: ["openkit-plugin"],
69
+ })
70
+ assert.equal(result.conflicts.length, 1)
71
+ assert.deepEqual(result.conflicts[0], {
72
+ field: "wrappers",
73
+ reason: "unclassified-top-level-key",
74
+ currentValue: undefined,
75
+ desiredValue: {
76
+ managed: true,
77
+ },
78
+ })
79
+ })
80
+
81
+ test("applyOpenKitMergePolicy preserves existing order while appending unique allowlisted values", () => {
82
+ const result = applyOpenKitMergePolicy({
83
+ currentConfig: {
84
+ plugin: ["alpha", "beta"],
85
+ instructions: ["LOCAL.md"],
86
+ },
87
+ desiredConfig: {
88
+ plugin: ["beta", "gamma", "alpha"],
89
+ instructions: ["OPENKIT.md", "LOCAL.md", "GUIDE.md"],
90
+ },
91
+ })
92
+
93
+ assert.deepEqual(result.config, {
94
+ plugin: ["alpha", "beta", "gamma"],
95
+ instructions: ["LOCAL.md", "OPENKIT.md", "GUIDE.md"],
96
+ })
97
+ assert.deepEqual(result.conflicts, [])
98
+ })
99
+
100
+ test("applyOpenKitMergePolicy additively inserts allowed managed-install top-level keys", () => {
101
+ const result = applyOpenKitMergePolicy({
102
+ currentConfig: {
103
+ plugin: ["existing-plugin"],
104
+ instructions: ["LOCAL.md"],
105
+ theme: "light",
106
+ },
107
+ desiredConfig: {
108
+ installState: {
109
+ path: ".openkit/openkit-install.json",
110
+ schema: "openkit/install-state@1",
111
+ },
112
+ productSurface: {
113
+ current: "global-openkit-install",
114
+ installReadiness: "managed",
115
+ installationMode: "openkit-managed",
116
+ },
117
+ },
118
+ })
119
+
120
+ assert.deepEqual(result.config, {
121
+ plugin: ["existing-plugin"],
122
+ instructions: ["LOCAL.md"],
123
+ theme: "light",
124
+ installState: {
125
+ path: ".openkit/openkit-install.json",
126
+ schema: "openkit/install-state@1",
127
+ },
128
+ productSurface: {
129
+ current: "global-openkit-install",
130
+ installReadiness: "managed",
131
+ installationMode: "openkit-managed",
132
+ },
133
+ })
134
+ assert.deepEqual(result.conflicts, [])
135
+ assert.deepEqual(result.appliedFields, ["installState", "productSurface"])
136
+ })
137
+
138
+ test("applyOpenKitMergePolicy treats allowlisted object fields as equal regardless of key order", () => {
139
+ const result = applyOpenKitMergePolicy({
140
+ currentConfig: {
141
+ installState: {
142
+ schema: "openkit/install-state@1",
143
+ path: ".openkit/openkit-install.json",
144
+ },
145
+ productSurface: {
146
+ installationMode: "openkit-managed",
147
+ installReadiness: "managed",
148
+ current: "global-openkit-install",
149
+ },
150
+ },
151
+ desiredConfig: {
152
+ installState: {
153
+ path: ".openkit/openkit-install.json",
154
+ schema: "openkit/install-state@1",
155
+ },
156
+ productSurface: {
157
+ current: "global-openkit-install",
158
+ installReadiness: "managed",
159
+ installationMode: "openkit-managed",
160
+ },
161
+ },
162
+ })
163
+
164
+ assert.deepEqual(result.config, {
165
+ installState: {
166
+ schema: "openkit/install-state@1",
167
+ path: ".openkit/openkit-install.json",
168
+ },
169
+ productSurface: {
170
+ installationMode: "openkit-managed",
171
+ installReadiness: "managed",
172
+ current: "global-openkit-install",
173
+ },
174
+ })
175
+ assert.deepEqual(result.conflicts, [])
176
+ assert.deepEqual(result.appliedFields, ["installState", "productSurface"])
177
+ })