@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,280 @@
|
|
|
1
|
+
const fs = require("fs")
|
|
2
|
+
const path = require("path")
|
|
3
|
+
|
|
4
|
+
function fail(message) {
|
|
5
|
+
const error = new Error(message)
|
|
6
|
+
error.isWorkItemStoreError = true
|
|
7
|
+
throw error
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function ensureDir(dirPath) {
|
|
11
|
+
fs.mkdirSync(dirPath, { recursive: true })
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function readJson(filePath, missingMessage) {
|
|
15
|
+
let raw
|
|
16
|
+
try {
|
|
17
|
+
raw = fs.readFileSync(filePath, "utf8")
|
|
18
|
+
} catch (error) {
|
|
19
|
+
if (error.code === "ENOENT" && missingMessage) {
|
|
20
|
+
fail(missingMessage)
|
|
21
|
+
}
|
|
22
|
+
fail(`Unable to read JSON at '${filePath}': ${error.message}`)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
try {
|
|
26
|
+
return JSON.parse(raw)
|
|
27
|
+
} catch (error) {
|
|
28
|
+
fail(`Malformed JSON at '${filePath}': ${error.message}`)
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function writeJson(filePath, value) {
|
|
33
|
+
ensureDir(path.dirname(filePath))
|
|
34
|
+
fs.writeFileSync(filePath, `${JSON.stringify(value, null, 2)}\n`, "utf8")
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function sortJsonValue(value) {
|
|
38
|
+
if (Array.isArray(value)) {
|
|
39
|
+
return value.map(sortJsonValue)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (value && typeof value === "object") {
|
|
43
|
+
return Object.keys(value)
|
|
44
|
+
.sort()
|
|
45
|
+
.reduce((sorted, key) => {
|
|
46
|
+
sorted[key] = sortJsonValue(value[key])
|
|
47
|
+
return sorted
|
|
48
|
+
}, {})
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return value
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function slugify(value) {
|
|
55
|
+
return String(value)
|
|
56
|
+
.trim()
|
|
57
|
+
.toLowerCase()
|
|
58
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
59
|
+
.replace(/^-+|-+$/g, "")
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function deriveWorkItemId(state) {
|
|
63
|
+
if (state && typeof state.work_item_id === "string" && state.work_item_id.length > 0) {
|
|
64
|
+
return state.work_item_id
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (!state || typeof state.feature_id !== "string" || state.feature_id.length === 0) {
|
|
68
|
+
fail("work item state must include a non-empty feature_id")
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return slugify(state.feature_id)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function resolvePaths(projectRoot) {
|
|
75
|
+
const opencodeDir = path.join(projectRoot, ".opencode")
|
|
76
|
+
const workItemsDir = path.join(opencodeDir, "work-items")
|
|
77
|
+
|
|
78
|
+
return {
|
|
79
|
+
opencodeDir,
|
|
80
|
+
workflowStatePath: path.join(opencodeDir, "workflow-state.json"),
|
|
81
|
+
workItemsDir,
|
|
82
|
+
indexPath: path.join(workItemsDir, "index.json"),
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function resolveWorkItemPaths(projectRoot, workItemId) {
|
|
87
|
+
const paths = resolvePaths(projectRoot)
|
|
88
|
+
const workItemDir = path.join(paths.workItemsDir, workItemId)
|
|
89
|
+
|
|
90
|
+
return {
|
|
91
|
+
...paths,
|
|
92
|
+
workItemDir,
|
|
93
|
+
statePath: path.join(workItemDir, "state.json"),
|
|
94
|
+
relativeStatePath: path.posix.join(".opencode", "work-items", workItemId, "state.json"),
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function createEmptyIndex() {
|
|
99
|
+
return {
|
|
100
|
+
active_work_item_id: null,
|
|
101
|
+
work_items: [],
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function bootstrapRuntimeStore(runtimeRoot) {
|
|
106
|
+
const paths = resolvePaths(runtimeRoot)
|
|
107
|
+
|
|
108
|
+
ensureDir(paths.workItemsDir)
|
|
109
|
+
|
|
110
|
+
if (fs.existsSync(paths.indexPath)) {
|
|
111
|
+
return readWorkItemIndex(runtimeRoot)
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (fs.existsSync(paths.workflowStatePath)) {
|
|
115
|
+
bootstrapLegacyWorkflowState(runtimeRoot)
|
|
116
|
+
return readWorkItemIndex(runtimeRoot)
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const index = createEmptyIndex()
|
|
120
|
+
writeWorkItemIndex(runtimeRoot, index)
|
|
121
|
+
return index
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function readWorkItemIndex(projectRoot) {
|
|
125
|
+
const { indexPath } = resolvePaths(projectRoot)
|
|
126
|
+
return readJson(indexPath, `Work-item index missing at '${indexPath}'`)
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function writeWorkItemIndex(projectRoot, index) {
|
|
130
|
+
const { indexPath } = resolvePaths(projectRoot)
|
|
131
|
+
writeJson(indexPath, index)
|
|
132
|
+
return index
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function readWorkItemState(projectRoot, workItemId) {
|
|
136
|
+
const { statePath } = resolveWorkItemPaths(projectRoot, workItemId)
|
|
137
|
+
return readJson(statePath, `Work-item state missing at '${statePath}'`)
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function writeWorkItemState(projectRoot, workItemId, state) {
|
|
141
|
+
const { statePath } = resolveWorkItemPaths(projectRoot, workItemId)
|
|
142
|
+
const nextState = {
|
|
143
|
+
...state,
|
|
144
|
+
work_item_id: deriveWorkItemId({
|
|
145
|
+
...state,
|
|
146
|
+
work_item_id: workItemId,
|
|
147
|
+
}),
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
writeJson(statePath, nextState)
|
|
151
|
+
return nextState
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function upsertIndexEntry(index, state, workItemId, relativeStatePath) {
|
|
155
|
+
const nextEntry = {
|
|
156
|
+
work_item_id: workItemId,
|
|
157
|
+
feature_id: state.feature_id,
|
|
158
|
+
feature_slug: state.feature_slug,
|
|
159
|
+
mode: state.mode,
|
|
160
|
+
status: state.status,
|
|
161
|
+
state_path: relativeStatePath,
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const existingIndex = index.work_items.findIndex((entry) => entry.work_item_id === workItemId)
|
|
165
|
+
if (existingIndex === -1) {
|
|
166
|
+
index.work_items.push(nextEntry)
|
|
167
|
+
} else {
|
|
168
|
+
index.work_items[existingIndex] = nextEntry
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
return nextEntry
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function readLegacyWorkflowState(projectRoot) {
|
|
175
|
+
const { workflowStatePath } = resolvePaths(projectRoot)
|
|
176
|
+
return readJson(workflowStatePath, `Compatibility workflow state missing at '${workflowStatePath}'`)
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function refreshCompatibilityMirror(projectRoot) {
|
|
180
|
+
const { workflowStatePath } = resolvePaths(projectRoot)
|
|
181
|
+
const index = readWorkItemIndex(projectRoot)
|
|
182
|
+
|
|
183
|
+
if (!index.active_work_item_id) {
|
|
184
|
+
fail("Cannot refresh compatibility mirror without an active work item")
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const activeState = readWorkItemState(projectRoot, index.active_work_item_id)
|
|
188
|
+
writeJson(workflowStatePath, activeState)
|
|
189
|
+
|
|
190
|
+
return activeState
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function writeCompatibilityMirror(projectRoot, state) {
|
|
194
|
+
const { workflowStatePath } = resolvePaths(projectRoot)
|
|
195
|
+
writeJson(workflowStatePath, state)
|
|
196
|
+
return state
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function bootstrapLegacyWorkflowState(projectRoot) {
|
|
200
|
+
const legacyState = readLegacyWorkflowState(projectRoot)
|
|
201
|
+
const workItemId = deriveWorkItemId(legacyState)
|
|
202
|
+
const workItemPaths = resolveWorkItemPaths(projectRoot, workItemId)
|
|
203
|
+
|
|
204
|
+
const index = fs.existsSync(workItemPaths.indexPath)
|
|
205
|
+
? readWorkItemIndex(projectRoot)
|
|
206
|
+
: createEmptyIndex()
|
|
207
|
+
|
|
208
|
+
const nextState = {
|
|
209
|
+
...legacyState,
|
|
210
|
+
work_item_id: workItemId,
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
writeWorkItemState(projectRoot, workItemId, nextState)
|
|
214
|
+
upsertIndexEntry(index, nextState, workItemId, workItemPaths.relativeStatePath)
|
|
215
|
+
index.active_work_item_id = workItemId
|
|
216
|
+
writeWorkItemIndex(projectRoot, index)
|
|
217
|
+
refreshCompatibilityMirror(projectRoot)
|
|
218
|
+
|
|
219
|
+
return {
|
|
220
|
+
workItemId,
|
|
221
|
+
index,
|
|
222
|
+
state: nextState,
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function setActiveWorkItem(projectRoot, workItemId) {
|
|
227
|
+
const index = readWorkItemIndex(projectRoot)
|
|
228
|
+
const entry = index.work_items.find((item) => item.work_item_id === workItemId)
|
|
229
|
+
|
|
230
|
+
if (!entry) {
|
|
231
|
+
fail(`Unknown work item '${workItemId}'`)
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
readWorkItemState(projectRoot, workItemId)
|
|
235
|
+
|
|
236
|
+
index.active_work_item_id = workItemId
|
|
237
|
+
writeWorkItemIndex(projectRoot, index)
|
|
238
|
+
|
|
239
|
+
return index
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function validateActiveMirror(projectRoot) {
|
|
243
|
+
const { workflowStatePath } = resolvePaths(projectRoot)
|
|
244
|
+
const index = readWorkItemIndex(projectRoot)
|
|
245
|
+
|
|
246
|
+
if (!index.active_work_item_id) {
|
|
247
|
+
fail("Active work item pointer missing")
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
if (!fs.existsSync(workflowStatePath)) {
|
|
251
|
+
fail("Compatibility mirror missing")
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
const mirrorState = readJson(workflowStatePath)
|
|
255
|
+
const activeState = readWorkItemState(projectRoot, index.active_work_item_id)
|
|
256
|
+
|
|
257
|
+
if (JSON.stringify(sortJsonValue(mirrorState)) !== JSON.stringify(sortJsonValue(activeState))) {
|
|
258
|
+
fail("Compatibility mirror diverged from active work item state")
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
return {
|
|
262
|
+
active_work_item_id: index.active_work_item_id,
|
|
263
|
+
mirror_state_path: workflowStatePath,
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
module.exports = {
|
|
268
|
+
bootstrapRuntimeStore,
|
|
269
|
+
bootstrapLegacyWorkflowState,
|
|
270
|
+
deriveWorkItemId,
|
|
271
|
+
readWorkItemIndex,
|
|
272
|
+
readWorkItemState,
|
|
273
|
+
refreshCompatibilityMirror,
|
|
274
|
+
resolveWorkItemPaths,
|
|
275
|
+
setActiveWorkItem,
|
|
276
|
+
validateActiveMirror,
|
|
277
|
+
writeCompatibilityMirror,
|
|
278
|
+
writeWorkItemIndex,
|
|
279
|
+
writeWorkItemState,
|
|
280
|
+
}
|