@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,63 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+
4
+ import { getWorkspacePaths } from './paths.js';
5
+
6
+ const WORKSPACE_STATE_SCHEMA = 'openkit/workspace-state@1';
7
+
8
+ function writeJson(filePath, value) {
9
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
10
+ fs.writeFileSync(filePath, `${JSON.stringify(value, null, 2)}\n`, 'utf8');
11
+ }
12
+
13
+ function readJson(filePath) {
14
+ return JSON.parse(fs.readFileSync(filePath, 'utf8'));
15
+ }
16
+
17
+ export function createWorkspaceMeta({ projectRoot, workspaceId, kitVersion = '0.1.0', profile = 'openkit' }) {
18
+ return {
19
+ schema: WORKSPACE_STATE_SCHEMA,
20
+ stateVersion: 1,
21
+ projectRoot,
22
+ workspaceId,
23
+ profile,
24
+ kitVersion,
25
+ createdAt: new Date().toISOString(),
26
+ updatedAt: new Date().toISOString(),
27
+ };
28
+ }
29
+
30
+ export function ensureWorkspaceBootstrap(options = {}) {
31
+ const paths = getWorkspacePaths(options);
32
+
33
+ fs.mkdirSync(paths.opencodeDir, { recursive: true });
34
+ fs.mkdirSync(paths.workItemsDir, { recursive: true });
35
+
36
+ if (!fs.existsSync(paths.workspaceMetaPath)) {
37
+ writeJson(
38
+ paths.workspaceMetaPath,
39
+ createWorkspaceMeta({
40
+ projectRoot: paths.projectRoot,
41
+ workspaceId: paths.workspaceId,
42
+ }),
43
+ );
44
+ }
45
+
46
+ if (!fs.existsSync(paths.workItemIndexPath)) {
47
+ writeJson(paths.workItemIndexPath, {
48
+ active_work_item_id: null,
49
+ work_items: [],
50
+ });
51
+ }
52
+
53
+ return paths;
54
+ }
55
+
56
+ export function readWorkspaceMeta(options = {}) {
57
+ const paths = ensureWorkspaceBootstrap(options);
58
+ return {
59
+ paths,
60
+ meta: readJson(paths.workspaceMetaPath),
61
+ index: readJson(paths.workItemIndexPath),
62
+ };
63
+ }
@@ -0,0 +1,284 @@
1
+ import fs from "node:fs"
2
+ import path from "node:path"
3
+
4
+ const OPENKIT_OPENCODE_BUNDLED_ASSETS = [
5
+ {
6
+ id: "opencode.bundle.readme",
7
+ assetClass: "bundle-metadata",
8
+ sourcePath: "assets/install-bundle/opencode/README.md",
9
+ bundledPath: "assets/install-bundle/opencode/README.md",
10
+ },
11
+ {
12
+ id: "opencode.agent.ArchitectAgent",
13
+ assetClass: "agents",
14
+ sourcePath: "agents/architect-agent.md",
15
+ bundledPath: "assets/install-bundle/opencode/agents/ArchitectAgent.md",
16
+ },
17
+ {
18
+ id: "opencode.agent.BAAgent",
19
+ assetClass: "agents",
20
+ sourcePath: "agents/ba-agent.md",
21
+ bundledPath: "assets/install-bundle/opencode/agents/BAAgent.md",
22
+ },
23
+ {
24
+ id: "opencode.agent.CodeReviewer",
25
+ assetClass: "agents",
26
+ sourcePath: "agents/code-reviewer.md",
27
+ bundledPath: "assets/install-bundle/opencode/agents/CodeReviewer.md",
28
+ },
29
+ {
30
+ id: "opencode.agent.FullstackAgent",
31
+ assetClass: "agents",
32
+ sourcePath: "agents/fullstack-agent.md",
33
+ bundledPath: "assets/install-bundle/opencode/agents/FullstackAgent.md",
34
+ },
35
+ {
36
+ id: "opencode.agent.MasterOrchestrator",
37
+ assetClass: "agents",
38
+ sourcePath: "agents/master-orchestrator.md",
39
+ bundledPath: "assets/install-bundle/opencode/agents/MasterOrchestrator.md",
40
+ },
41
+ {
42
+ id: "opencode.agent.PMAgent",
43
+ assetClass: "agents",
44
+ sourcePath: "agents/pm-agent.md",
45
+ bundledPath: "assets/install-bundle/opencode/agents/PMAgent.md",
46
+ },
47
+ {
48
+ id: "opencode.agent.QAAgent",
49
+ assetClass: "agents",
50
+ sourcePath: "agents/qa-agent.md",
51
+ bundledPath: "assets/install-bundle/opencode/agents/QAAgent.md",
52
+ },
53
+ {
54
+ id: "opencode.agent.TechLeadAgent",
55
+ assetClass: "agents",
56
+ sourcePath: "agents/tech-lead-agent.md",
57
+ bundledPath: "assets/install-bundle/opencode/agents/TechLeadAgent.md",
58
+ },
59
+ {
60
+ id: "opencode.command.brainstorm",
61
+ assetClass: "commands",
62
+ sourcePath: "commands/brainstorm.md",
63
+ bundledPath: "assets/install-bundle/opencode/commands/brainstorm.md",
64
+ },
65
+ {
66
+ id: "opencode.command.delivery",
67
+ assetClass: "commands",
68
+ sourcePath: "commands/delivery.md",
69
+ bundledPath: "assets/install-bundle/opencode/commands/delivery.md",
70
+ },
71
+ {
72
+ id: "opencode.command.execute-plan",
73
+ assetClass: "commands",
74
+ sourcePath: "commands/execute-plan.md",
75
+ bundledPath: "assets/install-bundle/opencode/commands/execute-plan.md",
76
+ },
77
+ {
78
+ id: "opencode.command.migrate",
79
+ assetClass: "commands",
80
+ sourcePath: "commands/migrate.md",
81
+ bundledPath: "assets/install-bundle/opencode/commands/migrate.md",
82
+ },
83
+ {
84
+ id: "opencode.command.quick-task",
85
+ assetClass: "commands",
86
+ sourcePath: "commands/quick-task.md",
87
+ bundledPath: "assets/install-bundle/opencode/commands/quick-task.md",
88
+ },
89
+ {
90
+ id: "opencode.command.task",
91
+ assetClass: "commands",
92
+ sourcePath: "commands/task.md",
93
+ bundledPath: "assets/install-bundle/opencode/commands/task.md",
94
+ },
95
+ {
96
+ id: "opencode.command.write-plan",
97
+ assetClass: "commands",
98
+ sourcePath: "commands/write-plan.md",
99
+ bundledPath: "assets/install-bundle/opencode/commands/write-plan.md",
100
+ },
101
+ {
102
+ id: "opencode.context.lane-selection",
103
+ assetClass: "context",
104
+ sourcePath: "context/core/lane-selection.md",
105
+ bundledPath: "assets/install-bundle/opencode/context/core/lane-selection.md",
106
+ },
107
+ {
108
+ id: "opencode.skill.brainstorming",
109
+ assetClass: "skills",
110
+ sourcePath: "skills/brainstorming/SKILL.md",
111
+ bundledPath: "assets/install-bundle/opencode/skills/brainstorming/SKILL.md",
112
+ },
113
+ {
114
+ id: "opencode.skill.code-review",
115
+ assetClass: "skills",
116
+ sourcePath: "skills/code-review/SKILL.md",
117
+ bundledPath: "assets/install-bundle/opencode/skills/code-review/SKILL.md",
118
+ },
119
+ {
120
+ id: "opencode.skill.subagent-driven-development",
121
+ assetClass: "skills",
122
+ sourcePath: "skills/subagent-driven-development/SKILL.md",
123
+ bundledPath: "assets/install-bundle/opencode/skills/subagent-driven-development/SKILL.md",
124
+ },
125
+ {
126
+ id: "opencode.skill.systematic-debugging",
127
+ assetClass: "skills",
128
+ sourcePath: "skills/systematic-debugging/SKILL.md",
129
+ bundledPath: "assets/install-bundle/opencode/skills/systematic-debugging/SKILL.md",
130
+ },
131
+ {
132
+ id: "opencode.skill.test-driven-development",
133
+ assetClass: "skills",
134
+ sourcePath: "skills/test-driven-development/SKILL.md",
135
+ bundledPath: "assets/install-bundle/opencode/skills/test-driven-development/SKILL.md",
136
+ },
137
+ {
138
+ id: "opencode.skill.using-skills",
139
+ assetClass: "skills",
140
+ sourcePath: "skills/using-skills/SKILL.md",
141
+ bundledPath: "assets/install-bundle/opencode/skills/using-skills/SKILL.md",
142
+ },
143
+ {
144
+ id: "opencode.skill.verification-before-completion",
145
+ assetClass: "skills",
146
+ sourcePath: "skills/verification-before-completion/SKILL.md",
147
+ bundledPath: "assets/install-bundle/opencode/skills/verification-before-completion/SKILL.md",
148
+ },
149
+ {
150
+ id: "opencode.skill.writing-plans",
151
+ assetClass: "skills",
152
+ sourcePath: "skills/writing-plans/SKILL.md",
153
+ bundledPath: "assets/install-bundle/opencode/skills/writing-plans/SKILL.md",
154
+ },
155
+ {
156
+ id: "opencode.skill.writing-specs",
157
+ assetClass: "skills",
158
+ sourcePath: "skills/writing-specs/SKILL.md",
159
+ bundledPath: "assets/install-bundle/opencode/skills/writing-specs/SKILL.md",
160
+ },
161
+ ]
162
+
163
+ export const OPENKIT_ASSET_MANIFEST = {
164
+ schema: "openkit/asset-manifest@1",
165
+ manifestVersion: 1,
166
+ bundle: {
167
+ namespace: "openkit",
168
+ profile: "openkit-global-install",
169
+ phase: 1,
170
+ derivedFrom: ["agents/", "commands/", "skills/"],
171
+ includedAssetClasses: ["agents", "commands", "context", "skills"],
172
+ deferredAssetClasses: ["plugins", "package.json"],
173
+ collisionPolicy: {
174
+ installNamespace: "openkit",
175
+ assetIdPrefix: "opencode",
176
+ onCollision: "fail-closed-and-require-explicit-mapping",
177
+ rationale:
178
+ "Phase 1 ships an explicit namespaced bundle instead of overwriting unrelated OpenCode-native assets.",
179
+ },
180
+ assets: OPENKIT_OPENCODE_BUNDLED_ASSETS,
181
+ },
182
+ assets: [
183
+ {
184
+ id: "runtime.opencode-manifest",
185
+ path: "opencode.json",
186
+ kind: "template",
187
+ templatePath: "assets/opencode.json.template",
188
+ phase: 1,
189
+ required: true,
190
+ adoptionAllowed: true,
191
+ description: "Managed install entrypoint manifest for OpenKit installs.",
192
+ },
193
+ {
194
+ id: "runtime.install-state",
195
+ path: ".openkit/openkit-install.json",
196
+ kind: "template",
197
+ templatePath: "assets/openkit-install.json.template",
198
+ phase: 1,
199
+ required: true,
200
+ adoptionAllowed: false,
201
+ description: "OpenKit-managed install state persisted in the target repository.",
202
+ },
203
+ ],
204
+ }
205
+
206
+ export function listManagedAssetIds() {
207
+ return OPENKIT_ASSET_MANIFEST.assets.map((asset) => asset.id)
208
+ }
209
+
210
+ export function listBundledAssetIds() {
211
+ return OPENKIT_ASSET_MANIFEST.bundle.assets.map((asset) => asset.id)
212
+ }
213
+
214
+ export function getManagedAsset(assetId) {
215
+ return OPENKIT_ASSET_MANIFEST.assets.find((asset) => asset.id === assetId) ?? null
216
+ }
217
+
218
+ export function validateBundledAssetFiles(projectRoot) {
219
+ const bundleRoot = path.join(projectRoot, "assets", "install-bundle", "opencode")
220
+ const missingFiles = []
221
+ const mismatchedFiles = []
222
+
223
+ for (const asset of OPENKIT_ASSET_MANIFEST.bundle.assets) {
224
+ const sourcePath = path.join(projectRoot, asset.sourcePath)
225
+ const bundledPath = path.join(projectRoot, asset.bundledPath)
226
+ const hasSourceFile = fs.existsSync(sourcePath)
227
+ const hasBundledFile = fs.existsSync(bundledPath)
228
+
229
+ if (!hasSourceFile) {
230
+ missingFiles.push(asset.sourcePath)
231
+ }
232
+
233
+ if (!hasBundledFile) {
234
+ missingFiles.push(asset.bundledPath)
235
+ }
236
+
237
+ if (hasSourceFile && hasBundledFile) {
238
+ const sourceContents = fs.readFileSync(sourcePath, "utf8")
239
+ const bundledContents = fs.readFileSync(bundledPath, "utf8")
240
+
241
+ if (sourceContents !== bundledContents) {
242
+ mismatchedFiles.push({
243
+ id: asset.id,
244
+ sourcePath: asset.sourcePath,
245
+ bundledPath: asset.bundledPath,
246
+ })
247
+ }
248
+ }
249
+ }
250
+
251
+ const bundledFiles = []
252
+
253
+ function collectFiles(currentPath) {
254
+ for (const entry of fs.readdirSync(currentPath, { withFileTypes: true })) {
255
+ const entryPath = path.join(currentPath, entry.name)
256
+
257
+ if (entry.isDirectory()) {
258
+ collectFiles(entryPath)
259
+ continue
260
+ }
261
+
262
+ bundledFiles.push(path.relative(projectRoot, entryPath))
263
+ }
264
+ }
265
+
266
+ if (fs.existsSync(bundleRoot)) {
267
+ collectFiles(bundleRoot)
268
+ }
269
+
270
+ const expectedBundledFiles = new Set(
271
+ OPENKIT_ASSET_MANIFEST.bundle.assets.map((asset) => asset.bundledPath),
272
+ )
273
+
274
+ const extraBundledFiles = bundledFiles.filter((filePath) => !expectedBundledFiles.has(filePath))
275
+
276
+ return {
277
+ missingFiles,
278
+ mismatchedFiles,
279
+ bundleFileCount: OPENKIT_ASSET_MANIFEST.bundle.assets.length,
280
+ expectedBundledFiles: [...expectedBundledFiles],
281
+ actualBundledFiles: bundledFiles,
282
+ extraBundledFiles,
283
+ }
284
+ }
@@ -0,0 +1,43 @@
1
+ export function createMaterializationConflict({
2
+ assetId,
3
+ path,
4
+ field,
5
+ reason,
6
+ currentValue,
7
+ desiredValue,
8
+ resolution = "manual-review-required",
9
+ }) {
10
+ const conflict = {
11
+ assetId,
12
+ path,
13
+ reason,
14
+ resolution,
15
+ }
16
+
17
+ if (field !== undefined) {
18
+ conflict.field = field
19
+ }
20
+
21
+ if (currentValue !== undefined) {
22
+ conflict.currentValue = currentValue
23
+ }
24
+
25
+ if (desiredValue !== undefined) {
26
+ conflict.desiredValue = desiredValue
27
+ }
28
+
29
+ return conflict
30
+ }
31
+
32
+ export function qualifyMergeConflicts(mergeConflicts, assetId, assetPath) {
33
+ return mergeConflicts.map((conflict) =>
34
+ createMaterializationConflict({
35
+ assetId,
36
+ path: assetPath,
37
+ field: conflict.field,
38
+ reason: conflict.reason,
39
+ currentValue: conflict.currentValue,
40
+ desiredValue: conflict.desiredValue,
41
+ }),
42
+ )
43
+ }
@@ -0,0 +1,138 @@
1
+ import fs from "node:fs"
2
+ import path from "node:path"
3
+
4
+ function fileExists(filePath) {
5
+ return fs.existsSync(filePath)
6
+ }
7
+
8
+ function safeReadJson(filePath) {
9
+ if (!fileExists(filePath)) {
10
+ return {
11
+ exists: false,
12
+ value: null,
13
+ malformed: false,
14
+ }
15
+ }
16
+
17
+ try {
18
+ return {
19
+ exists: true,
20
+ value: JSON.parse(fs.readFileSync(filePath, "utf8")),
21
+ malformed: false,
22
+ }
23
+ } catch {
24
+ return {
25
+ exists: true,
26
+ value: null,
27
+ malformed: true,
28
+ }
29
+ }
30
+ }
31
+
32
+ function classifyProjectShape({
33
+ hasRuntimeManifest,
34
+ hasRootInstallEntrypoint,
35
+ hasInstallManifest,
36
+ hasRegistry,
37
+ installManifest,
38
+ registry,
39
+ malformedMetadata,
40
+ }) {
41
+ const notes = []
42
+
43
+ if (malformedMetadata.installManifest) {
44
+ notes.push("Install manifest metadata is malformed and could not be parsed.")
45
+ }
46
+
47
+ if (malformedMetadata.registry) {
48
+ notes.push("Registry metadata is malformed and could not be parsed.")
49
+ }
50
+
51
+ if (hasRuntimeManifest && hasRootInstallEntrypoint) {
52
+ return {
53
+ classification: "mixed-install-surfaces",
54
+ notes: [
55
+ ...notes,
56
+ "Detected both repository-local runtime and root install entrypoint surfaces; treat install adoption as mixed and review manually.",
57
+ ],
58
+ }
59
+ }
60
+
61
+ const installMode = installManifest?.installation?.mode
62
+ const emergingSurface = registry?.kit?.productSurface?.emerging
63
+
64
+ if (
65
+ hasRuntimeManifest &&
66
+ hasInstallManifest &&
67
+ hasRegistry &&
68
+ installMode === "additive-non-destructive" &&
69
+ emergingSurface === "global-openkit-install"
70
+ ) {
71
+ return {
72
+ classification: "openkit-additive-local-metadata",
73
+ notes,
74
+ }
75
+ }
76
+
77
+ if (hasRuntimeManifest && !hasInstallManifest && !hasRegistry) {
78
+ return {
79
+ classification: "opencode-runtime-only",
80
+ notes,
81
+ }
82
+ }
83
+
84
+ if (!hasRuntimeManifest && !hasRootInstallEntrypoint) {
85
+ return {
86
+ classification: "unknown",
87
+ notes: [...notes, "No recognized runtime manifest or install entrypoint was found."],
88
+ }
89
+ }
90
+
91
+ return {
92
+ classification: "unclassified",
93
+ notes: [...notes, "Detected a partial or unsupported runtime surface combination."],
94
+ }
95
+ }
96
+
97
+ export function discoverProjectShape(projectRoot) {
98
+ const runtimeManifestPath = path.join(projectRoot, ".opencode", "opencode.json")
99
+ const rootInstallEntrypointPath = path.join(projectRoot, "opencode.json")
100
+ const installManifestPath = path.join(projectRoot, ".opencode", "install-manifest.json")
101
+ const registryPath = path.join(projectRoot, "registry.json")
102
+
103
+ const hasRuntimeManifest = fileExists(runtimeManifestPath)
104
+ const hasRootInstallEntrypoint = fileExists(rootInstallEntrypointPath)
105
+ const hasInstallManifest = fileExists(installManifestPath)
106
+ const hasRegistry = fileExists(registryPath)
107
+
108
+ const installManifestResult = safeReadJson(installManifestPath)
109
+ const registryResult = safeReadJson(registryPath)
110
+ const malformedMetadata = {
111
+ installManifest: installManifestResult.malformed,
112
+ registry: registryResult.malformed,
113
+ }
114
+ const classification = classifyProjectShape({
115
+ hasRuntimeManifest,
116
+ hasRootInstallEntrypoint,
117
+ hasInstallManifest,
118
+ hasRegistry,
119
+ installManifest: installManifestResult.value,
120
+ registry: registryResult.value,
121
+ malformedMetadata,
122
+ })
123
+
124
+ return {
125
+ projectRoot,
126
+ runtimeManifestPath,
127
+ rootInstallEntrypointPath,
128
+ installManifestPath,
129
+ registryPath,
130
+ hasRuntimeManifest,
131
+ hasRootInstallEntrypoint,
132
+ hasInstallManifest,
133
+ hasRegistry,
134
+ malformedMetadata,
135
+ classification: classification.classification,
136
+ notes: classification.notes,
137
+ }
138
+ }
@@ -0,0 +1,136 @@
1
+ export const INSTALL_STATE_SCHEMA = "openkit/install-state@1"
2
+
3
+ const MANAGED_STATUSES = new Set(["managed", "materialized"])
4
+ const ISO_8601_TIMESTAMP_PATTERN = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{3})?Z$/
5
+
6
+ function createIsoTimestamp(now = new Date()) {
7
+ return now.toISOString()
8
+ }
9
+
10
+ function isNonEmptyString(value) {
11
+ return typeof value === "string" && value.length > 0
12
+ }
13
+
14
+ function isIsoTimestamp(value) {
15
+ return isNonEmptyString(value) && ISO_8601_TIMESTAMP_PATTERN.test(value)
16
+ }
17
+
18
+ function normalizeArray(value) {
19
+ return Array.isArray(value) ? value : []
20
+ }
21
+
22
+ function isArray(value) {
23
+ return Array.isArray(value)
24
+ }
25
+
26
+ export function createInstallState({
27
+ kitVersion = "0.1.0",
28
+ profile = "openkit-core",
29
+ managedAssets = [],
30
+ adoptedAssets = [],
31
+ warnings = [],
32
+ conflicts = [],
33
+ now,
34
+ } = {}) {
35
+ return {
36
+ schema: INSTALL_STATE_SCHEMA,
37
+ stateVersion: 1,
38
+ kit: {
39
+ name: "OpenKit",
40
+ version: kitVersion,
41
+ },
42
+ installation: {
43
+ profile,
44
+ status: "installed",
45
+ installedAt: createIsoTimestamp(now),
46
+ },
47
+ assets: {
48
+ managed: normalizeArray(managedAssets),
49
+ adopted: normalizeArray(adoptedAssets),
50
+ },
51
+ warnings: normalizeArray(warnings),
52
+ conflicts: normalizeArray(conflicts),
53
+ }
54
+ }
55
+
56
+ export function validateInstallState(state) {
57
+ const errors = []
58
+
59
+ if (!state || typeof state !== "object") {
60
+ return ["install state must be an object"]
61
+ }
62
+
63
+ if (state.schema !== INSTALL_STATE_SCHEMA) {
64
+ errors.push(`schema must be '${INSTALL_STATE_SCHEMA}'`)
65
+ }
66
+
67
+ if (state.stateVersion !== 1) {
68
+ errors.push("stateVersion must be 1")
69
+ }
70
+
71
+ if (!isNonEmptyString(state.kit?.name)) {
72
+ errors.push("kit.name must be a non-empty string")
73
+ }
74
+
75
+ if (!isNonEmptyString(state.kit?.version)) {
76
+ errors.push("kit.version must be a non-empty string")
77
+ }
78
+
79
+ if (!isNonEmptyString(state.installation?.profile)) {
80
+ errors.push("installation.profile must be a non-empty string")
81
+ }
82
+
83
+ if (state.installation?.status !== "installed") {
84
+ errors.push("installation.status must be 'installed'")
85
+ }
86
+
87
+ if (!isIsoTimestamp(state.installation?.installedAt)) {
88
+ errors.push("installation.installedAt must be an ISO-8601 timestamp")
89
+ }
90
+
91
+ if (!isArray(state.assets?.managed)) {
92
+ errors.push("assets.managed must be an array")
93
+ } else {
94
+ state.assets.managed.forEach((asset, index) => {
95
+ if (!isNonEmptyString(asset?.path)) {
96
+ errors.push(`assets.managed[${index}].path must be a non-empty string`)
97
+ }
98
+
99
+ if (!MANAGED_STATUSES.has(asset?.status)) {
100
+ errors.push(`assets.managed[${index}].status must be one of: managed, materialized`)
101
+ }
102
+ })
103
+ }
104
+
105
+ if (!isArray(state.assets?.adopted)) {
106
+ errors.push("assets.adopted must be an array")
107
+ } else {
108
+ state.assets.adopted.forEach((asset, index) => {
109
+ if (asset?.status !== "adopted") {
110
+ errors.push(`assets.adopted[${index}].status must be 'adopted'`)
111
+ }
112
+ })
113
+ }
114
+
115
+ if (!isArray(state.warnings)) {
116
+ errors.push("warnings must be an array")
117
+ } else {
118
+ state.warnings.forEach((warning, index) => {
119
+ if (!isNonEmptyString(warning?.code)) {
120
+ errors.push(`warnings[${index}].code must be a non-empty string`)
121
+ }
122
+ })
123
+ }
124
+
125
+ if (!isArray(state.conflicts)) {
126
+ errors.push("conflicts must be an array")
127
+ } else {
128
+ state.conflicts.forEach((conflict, index) => {
129
+ if (!isNonEmptyString(conflict?.reason)) {
130
+ errors.push(`conflicts[${index}].reason must be a non-empty string`)
131
+ }
132
+ })
133
+ }
134
+
135
+ return errors
136
+ }