@rembr/vscode 1.0.1 → 2.0.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/CHANGELOG.md +57 -1
- package/README.md +261 -148
- package/cli.js +29 -14
- package/package.json +2 -2
- package/setup.js +142 -17
- package/templates/agents/ralph-rlm.agent.md +164 -0
- package/templates/agents/rlm.agent.md +106 -0
- package/templates/copilot-instructions.md +66 -49
- package/templates/instructions/code-investigation.instructions.md +103 -0
- package/templates/instructions/rembr-integration.instructions.md +88 -0
- package/templates/prompts/ralph-analyze.prompt.md +74 -0
- package/templates/prompts/ralph-plan.prompt.md +70 -0
- package/templates/prompts/rlm-analyze.prompt.md +39 -0
- package/templates/prompts/rlm-plan.prompt.md +58 -0
- package/templates/recursive-analyst.agent.md +277 -0
- package/templates/skills/ralph-rlm-orchestration/SKILL.md +297 -0
- package/templates/skills/rlm-orchestration/SKILL.md +180 -0
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: ralph-rlm-orchestration
|
|
3
|
+
description: Use this skill for acceptance-driven coding tasks that must loop until explicit criteria are met. Combines RLM decomposition with Ralph Wiggum's validation loops and stuck detection.
|
|
4
|
+
license: MIT
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Ralph-RLM Orchestration Skill
|
|
8
|
+
|
|
9
|
+
This skill teaches you how to perform acceptance-driven RLM orchestration where you loop until ALL criteria are explicitly met and validated.
|
|
10
|
+
|
|
11
|
+
## When to Use This Skill
|
|
12
|
+
|
|
13
|
+
Activate this skill when:
|
|
14
|
+
- Task has quality requirements that must be validated
|
|
15
|
+
- Completion means meeting specific, measurable criteria
|
|
16
|
+
- You need automatic stuck detection and recovery
|
|
17
|
+
- Quality matters more than speed
|
|
18
|
+
|
|
19
|
+
## Core Principle
|
|
20
|
+
|
|
21
|
+
> "The plan is disposable. The acceptance criteria are not."
|
|
22
|
+
> — Ralph Wiggum approach
|
|
23
|
+
|
|
24
|
+
## Architecture
|
|
25
|
+
|
|
26
|
+
```
|
|
27
|
+
┌──────────────────────────────────────────────────────┐
|
|
28
|
+
│ RALPH-RLM LOOP │
|
|
29
|
+
│ ┌────────────────────────────────────────────────┐ │
|
|
30
|
+
│ │ 1. Load criteria from Rembr │ │
|
|
31
|
+
│ │ 2. Validate findings against criteria │ │
|
|
32
|
+
│ │ 3. If ALL met → COMPLETE │ │
|
|
33
|
+
│ │ 4. If NOT met → Decompose & investigate │ │
|
|
34
|
+
│ │ 5. Check stuck condition │ │
|
|
35
|
+
│ │ 6. If stuck → Regenerate plan │ │
|
|
36
|
+
│ │ 7. Update progress → Loop │ │
|
|
37
|
+
│ └────────────────────────────────────────────────┘ │
|
|
38
|
+
│ ↕ │
|
|
39
|
+
│ ┌────────────────────────────────────────────────┐ │
|
|
40
|
+
│ │ REMBR MCP │ │
|
|
41
|
+
│ │ goals: Acceptance criteria │ │
|
|
42
|
+
│ │ context: Progress, iteration state │ │
|
|
43
|
+
│ │ facts: Validated findings │ │
|
|
44
|
+
│ │ learning: Synthesis │ │
|
|
45
|
+
│ └────────────────────────────────────────────────┘ │
|
|
46
|
+
└──────────────────────────────────────────────────────┘
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Step-by-Step Process
|
|
50
|
+
|
|
51
|
+
### Step 1: Define Acceptance Criteria
|
|
52
|
+
|
|
53
|
+
**Before any work**, derive explicit criteria:
|
|
54
|
+
|
|
55
|
+
```javascript
|
|
56
|
+
const taskId = `ralph-rlm-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
57
|
+
|
|
58
|
+
const criteria = [
|
|
59
|
+
{
|
|
60
|
+
id: "AC1",
|
|
61
|
+
criterion: "All authentication endpoints use rate limiting",
|
|
62
|
+
evidenceRequired: "file:line showing rate limit middleware",
|
|
63
|
+
status: "pending"
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
id: "AC2",
|
|
67
|
+
criterion: "Passwords are hashed with bcrypt cost ≥10",
|
|
68
|
+
evidenceRequired: "file:line showing bcrypt configuration",
|
|
69
|
+
status: "pending"
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
id: "AC3",
|
|
73
|
+
criterion: "JWT tokens expire within 24 hours",
|
|
74
|
+
evidenceRequired: "file:line showing token expiration setting",
|
|
75
|
+
status: "pending"
|
|
76
|
+
}
|
|
77
|
+
];
|
|
78
|
+
|
|
79
|
+
await rembr.store({
|
|
80
|
+
category: "goals",
|
|
81
|
+
content: JSON.stringify(criteria),
|
|
82
|
+
metadata: {
|
|
83
|
+
taskId,
|
|
84
|
+
level: "L0",
|
|
85
|
+
type: "acceptance_criteria",
|
|
86
|
+
progress: { total: 3, met: 0 }
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### Step 2: Initialize Progress Tracking
|
|
92
|
+
|
|
93
|
+
```javascript
|
|
94
|
+
await rembr.store({
|
|
95
|
+
category: "context",
|
|
96
|
+
content: "Starting Ralph-RLM loop",
|
|
97
|
+
metadata: {
|
|
98
|
+
taskId,
|
|
99
|
+
type: "progress",
|
|
100
|
+
iteration: 1,
|
|
101
|
+
stuckCount: 0,
|
|
102
|
+
previousMetCount: 0,
|
|
103
|
+
status: "running"
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### Step 3: Main Loop
|
|
109
|
+
|
|
110
|
+
```
|
|
111
|
+
REPEAT:
|
|
112
|
+
// Load current state
|
|
113
|
+
criteria = await rembr.search({ taskId, category: "goals" })
|
|
114
|
+
progress = await rembr.search({ taskId, category: "context", type: "progress" })
|
|
115
|
+
|
|
116
|
+
// Check completion
|
|
117
|
+
metCount = criteria.filter(c => c.status === "met").length
|
|
118
|
+
IF metCount === criteria.length:
|
|
119
|
+
BREAK → COMPLETE
|
|
120
|
+
|
|
121
|
+
// Investigate unmet criteria
|
|
122
|
+
FOR EACH unmetCriterion:
|
|
123
|
+
// Store L1 criteria before investigation
|
|
124
|
+
await storeL1Criteria(unmetCriterion)
|
|
125
|
+
|
|
126
|
+
// Investigate with code tools
|
|
127
|
+
findings = await investigate(unmetCriterion)
|
|
128
|
+
|
|
129
|
+
// Validate against criterion
|
|
130
|
+
IF hasValidEvidence(findings, unmetCriterion):
|
|
131
|
+
await storeFinding(findings)
|
|
132
|
+
await updateCriterionStatus(unmetCriterion, "met")
|
|
133
|
+
|
|
134
|
+
// Check stuck condition
|
|
135
|
+
IF metCount === previousMetCount:
|
|
136
|
+
stuckCount++
|
|
137
|
+
IF stuckCount >= 3:
|
|
138
|
+
await regeneratePlan()
|
|
139
|
+
stuckCount = 0
|
|
140
|
+
ELSE:
|
|
141
|
+
stuckCount = 0
|
|
142
|
+
|
|
143
|
+
// Update progress
|
|
144
|
+
await updateProgress(iteration++, metCount, stuckCount)
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
### Step 4: Validation Rules
|
|
148
|
+
|
|
149
|
+
A finding is only valid when:
|
|
150
|
+
|
|
151
|
+
1. **Specific Evidence**: file:line reference or test output
|
|
152
|
+
2. **Matches Criterion**: Directly addresses the acceptance criterion
|
|
153
|
+
3. **Independently Verifiable**: Another agent could confirm it
|
|
154
|
+
|
|
155
|
+
```javascript
|
|
156
|
+
function validateFinding(finding, criterion) {
|
|
157
|
+
return (
|
|
158
|
+
finding.evidence?.length > 0 &&
|
|
159
|
+
finding.evidence.every(e => /^[\w/.]+:\d+$/.test(e)) &&
|
|
160
|
+
finding.criterion === criterion.id
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
### Step 5: Stuck Detection & Recovery
|
|
166
|
+
|
|
167
|
+
```javascript
|
|
168
|
+
async function checkStuck(currentMetCount, previousMetCount, stuckCount) {
|
|
169
|
+
if (currentMetCount === previousMetCount) {
|
|
170
|
+
stuckCount++;
|
|
171
|
+
if (stuckCount >= 3) {
|
|
172
|
+
console.log("STUCK DETECTED - Regenerating plan");
|
|
173
|
+
await regeneratePlan();
|
|
174
|
+
return 0; // Reset stuck count
|
|
175
|
+
}
|
|
176
|
+
} else {
|
|
177
|
+
return 0; // Progress made, reset
|
|
178
|
+
}
|
|
179
|
+
return stuckCount;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
async function regeneratePlan() {
|
|
183
|
+
// Load original criteria
|
|
184
|
+
const criteria = await rembr.search({ category: "goals" });
|
|
185
|
+
|
|
186
|
+
// Analyze what's blocking progress
|
|
187
|
+
const blockers = analyzeBlockers();
|
|
188
|
+
|
|
189
|
+
// Generate new decomposition approach
|
|
190
|
+
const newPlan = generateAlternativePlan(criteria, blockers);
|
|
191
|
+
|
|
192
|
+
// Store new plan
|
|
193
|
+
await rembr.store({
|
|
194
|
+
category: "context",
|
|
195
|
+
content: `Regenerated plan: ${JSON.stringify(newPlan)}`,
|
|
196
|
+
metadata: { type: "plan_regeneration" }
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
### Step 6: Synthesis on Completion
|
|
202
|
+
|
|
203
|
+
```javascript
|
|
204
|
+
async function synthesize(taskId) {
|
|
205
|
+
// Gather all validated findings
|
|
206
|
+
const findings = await rembr.search({
|
|
207
|
+
query: taskId,
|
|
208
|
+
category: "facts"
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
// Check for contradictions
|
|
212
|
+
const contradictions = findContradictions(findings);
|
|
213
|
+
|
|
214
|
+
// Store synthesis
|
|
215
|
+
await rembr.store({
|
|
216
|
+
category: "learning",
|
|
217
|
+
content: `Synthesis: All ${findings.length} criteria met. ${
|
|
218
|
+
contradictions.length ? `Contradictions: ${contradictions}` : 'No contradictions.'
|
|
219
|
+
}`,
|
|
220
|
+
metadata: {
|
|
221
|
+
taskId,
|
|
222
|
+
type: "synthesis",
|
|
223
|
+
status: "complete",
|
|
224
|
+
criteriaCount: findings.length
|
|
225
|
+
}
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
## Acceptance Criteria Templates
|
|
231
|
+
|
|
232
|
+
### Security Audit
|
|
233
|
+
```markdown
|
|
234
|
+
- AC1: All user inputs are sanitized before database queries
|
|
235
|
+
- AC2: Authentication uses industry-standard hashing (bcrypt/argon2)
|
|
236
|
+
- AC3: Session tokens have expiration ≤24h
|
|
237
|
+
- AC4: CORS is configured for specific origins only
|
|
238
|
+
- AC5: Sensitive data is encrypted at rest
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
### Feature Implementation
|
|
242
|
+
```markdown
|
|
243
|
+
- AC1: Feature handles happy path correctly
|
|
244
|
+
- AC2: Feature handles all error cases gracefully
|
|
245
|
+
- AC3: Feature has ≥80% test coverage
|
|
246
|
+
- AC4: Feature is documented in README
|
|
247
|
+
- AC5: Feature follows existing code patterns
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
### Documentation
|
|
251
|
+
```markdown
|
|
252
|
+
- AC1: All public APIs are documented
|
|
253
|
+
- AC2: All configuration options are explained
|
|
254
|
+
- AC3: Setup instructions are complete and tested
|
|
255
|
+
- AC4: Examples are provided for common use cases
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
## The 9s (Guardrails)
|
|
259
|
+
|
|
260
|
+
| Rule | Description |
|
|
261
|
+
|------|-------------|
|
|
262
|
+
| 99 | Evidence required - never claim without proof |
|
|
263
|
+
| 999 | Update progress every iteration |
|
|
264
|
+
| 9999 | Check criteria before claiming completion |
|
|
265
|
+
| 99999 | Store learnings for future reference |
|
|
266
|
+
| 999999 | Regenerate plan if stuck 3+ iterations |
|
|
267
|
+
| 9999999 | Never exit until ALL criteria validated |
|
|
268
|
+
|
|
269
|
+
## Example: Security Audit with Ralph-RLM
|
|
270
|
+
|
|
271
|
+
```
|
|
272
|
+
Task: "Audit authentication system for OWASP compliance"
|
|
273
|
+
|
|
274
|
+
Acceptance Criteria:
|
|
275
|
+
- AC1: No SQL injection vulnerabilities
|
|
276
|
+
- AC2: Passwords hashed with bcrypt cost ≥12
|
|
277
|
+
- AC3: Session tokens use secure random generation
|
|
278
|
+
- AC4: Rate limiting on login endpoints
|
|
279
|
+
- AC5: No sensitive data in logs
|
|
280
|
+
|
|
281
|
+
Iteration 1:
|
|
282
|
+
- Investigated AC1: Found parameterized queries ✅
|
|
283
|
+
- Investigated AC2: Found bcrypt cost = 10 ❌
|
|
284
|
+
- Progress: 1/5 met
|
|
285
|
+
|
|
286
|
+
Iteration 2:
|
|
287
|
+
- Investigated AC2 again: Confirmed cost = 10
|
|
288
|
+
- Investigated AC3: Found crypto.randomBytes() ✅
|
|
289
|
+
- Progress: 2/5 met
|
|
290
|
+
|
|
291
|
+
Iteration 3:
|
|
292
|
+
- Investigated AC4: Found express-rate-limit ✅
|
|
293
|
+
- Investigated AC5: Found password logged! ❌
|
|
294
|
+
- Progress: 3/5 met
|
|
295
|
+
|
|
296
|
+
[Loop continues until all criteria addressed]
|
|
297
|
+
```
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: rlm-orchestration
|
|
3
|
+
description: Use this skill when decomposing complex coding tasks into subagent investigations. Applies RLM (Recursive Language Model) patterns for hierarchical task breakdown with Rembr state coordination.
|
|
4
|
+
license: MIT
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# RLM Orchestration Skill
|
|
8
|
+
|
|
9
|
+
This skill teaches you how to perform Recursive Language Model (RLM) orchestration for complex coding tasks.
|
|
10
|
+
|
|
11
|
+
## When to Use This Skill
|
|
12
|
+
|
|
13
|
+
Activate this skill when:
|
|
14
|
+
- Task requires analyzing multiple files or systems
|
|
15
|
+
- Investigation needs structured decomposition
|
|
16
|
+
- Results should be persisted and synthesized
|
|
17
|
+
- Subtasks can be investigated independently
|
|
18
|
+
|
|
19
|
+
## RLM Architecture
|
|
20
|
+
|
|
21
|
+
```
|
|
22
|
+
USER TASK
|
|
23
|
+
↓
|
|
24
|
+
┌─────────────────────────────────────┐
|
|
25
|
+
│ L0 ORCHESTRATOR │
|
|
26
|
+
│ - Decompose task │
|
|
27
|
+
│ - Store context in Rembr │
|
|
28
|
+
│ - Coordinate subagents │
|
|
29
|
+
│ - Synthesize results │
|
|
30
|
+
└─────────────────────────────────────┘
|
|
31
|
+
↓ ↓ ↓
|
|
32
|
+
┌─────────┐ ┌─────────┐ ┌─────────┐
|
|
33
|
+
│ L1 Sub │ │ L1 Sub │ │ L1 Sub │
|
|
34
|
+
│ Agent │ │ Agent │ │ Agent │
|
|
35
|
+
└─────────┘ └─────────┘ └─────────┘
|
|
36
|
+
↓ ↓ ↓
|
|
37
|
+
┌─────────────────────────────────────┐
|
|
38
|
+
│ REMBR MCP │
|
|
39
|
+
│ Persistent State Coordinator │
|
|
40
|
+
│ - goals, context, facts, learning │
|
|
41
|
+
└─────────────────────────────────────┘
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Step-by-Step Process
|
|
45
|
+
|
|
46
|
+
### Step 1: Initialize Task
|
|
47
|
+
|
|
48
|
+
```javascript
|
|
49
|
+
// Generate unique task ID
|
|
50
|
+
const taskId = `rlm-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
51
|
+
|
|
52
|
+
// Store in Rembr
|
|
53
|
+
await rembr.store({
|
|
54
|
+
category: "context",
|
|
55
|
+
content: `Task: ${taskDescription}`,
|
|
56
|
+
metadata: {
|
|
57
|
+
taskId,
|
|
58
|
+
level: "L0",
|
|
59
|
+
type: "task_initialization",
|
|
60
|
+
status: "in_progress"
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### Step 2: Decompose Task
|
|
66
|
+
|
|
67
|
+
Analyze the task and identify 2-5 independent subtasks:
|
|
68
|
+
|
|
69
|
+
```markdown
|
|
70
|
+
## Decomposition for: [Task Name]
|
|
71
|
+
|
|
72
|
+
### Subtask 1: [Name]
|
|
73
|
+
- Focus: [Single clear objective]
|
|
74
|
+
- Scope: [Files/areas to investigate]
|
|
75
|
+
- Output: [Expected finding type]
|
|
76
|
+
|
|
77
|
+
### Subtask 2: [Name]
|
|
78
|
+
...
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### Step 3: Execute Subtasks
|
|
82
|
+
|
|
83
|
+
For each subtask:
|
|
84
|
+
|
|
85
|
+
1. **Create Context Snapshot**
|
|
86
|
+
```javascript
|
|
87
|
+
await rembr.store({
|
|
88
|
+
category: "context",
|
|
89
|
+
content: `Subtask: ${subtaskDescription}`,
|
|
90
|
+
metadata: { taskId, level: "L1", subtaskId, parentId: taskId }
|
|
91
|
+
});
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
2. **Investigate with Code Tools**
|
|
95
|
+
```bash
|
|
96
|
+
# Search for patterns
|
|
97
|
+
rg "pattern" --type ts
|
|
98
|
+
|
|
99
|
+
# Find relevant files
|
|
100
|
+
find . -name "*.ts" -path "*auth*"
|
|
101
|
+
|
|
102
|
+
# Extract specific content
|
|
103
|
+
sed -n '10,50p' src/auth/handler.ts
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
3. **Store Validated Findings**
|
|
107
|
+
```javascript
|
|
108
|
+
await rembr.store({
|
|
109
|
+
category: "facts",
|
|
110
|
+
content: `Finding: ${description}`,
|
|
111
|
+
metadata: {
|
|
112
|
+
taskId,
|
|
113
|
+
subtaskId,
|
|
114
|
+
evidence: ["src/auth/handler.ts:42", "src/auth/config.ts:15"],
|
|
115
|
+
confidence: 0.95
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### Step 4: Synthesize Results
|
|
121
|
+
|
|
122
|
+
```javascript
|
|
123
|
+
// Search for all findings
|
|
124
|
+
const findings = await rembr.search({
|
|
125
|
+
query: taskId,
|
|
126
|
+
category: "facts"
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
// Identify patterns
|
|
130
|
+
const patterns = analyzeFindings(findings);
|
|
131
|
+
|
|
132
|
+
// Store synthesis
|
|
133
|
+
await rembr.store({
|
|
134
|
+
category: "learning",
|
|
135
|
+
content: `Synthesis: ${synthesizedResult}`,
|
|
136
|
+
metadata: {
|
|
137
|
+
taskId,
|
|
138
|
+
type: "synthesis",
|
|
139
|
+
status: "complete",
|
|
140
|
+
findingCount: findings.length
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
## Rembr Categories Reference
|
|
146
|
+
|
|
147
|
+
| Category | Purpose | Example Content |
|
|
148
|
+
|----------|---------|-----------------|
|
|
149
|
+
| `goals` | Acceptance criteria, objectives | "Must identify all SQL injection points" |
|
|
150
|
+
| `context` | Task state, progress, plans | "Analyzing auth module, iteration 2" |
|
|
151
|
+
| `facts` | Validated findings with evidence | "Found hardcoded secret at config.ts:42" |
|
|
152
|
+
| `learning` | Synthesized insights | "Auth uses 3 different patterns..." |
|
|
153
|
+
|
|
154
|
+
## Best Practices
|
|
155
|
+
|
|
156
|
+
1. **Store Early, Store Often**: Put findings in Rembr immediately
|
|
157
|
+
2. **Evidence First**: Always include file:line references
|
|
158
|
+
3. **Focused Subtasks**: One clear objective per subtask
|
|
159
|
+
4. **Progress Updates**: Track state after each major step
|
|
160
|
+
5. **No Speculation**: Only report verified findings
|
|
161
|
+
|
|
162
|
+
## Example: Security Audit
|
|
163
|
+
|
|
164
|
+
```
|
|
165
|
+
Task: "Analyze authentication system for vulnerabilities"
|
|
166
|
+
|
|
167
|
+
Subtask 1: Credential Storage
|
|
168
|
+
- Search: rg "password|secret|key" --type ts
|
|
169
|
+
- Focus: How are credentials stored?
|
|
170
|
+
|
|
171
|
+
Subtask 2: Session Management
|
|
172
|
+
- Search: rg "session|token|jwt" --type ts
|
|
173
|
+
- Focus: Session lifecycle and expiration
|
|
174
|
+
|
|
175
|
+
Subtask 3: Input Validation
|
|
176
|
+
- Search: rg "validate|sanitize|escape" --type ts
|
|
177
|
+
- Focus: Are inputs properly validated?
|
|
178
|
+
|
|
179
|
+
Synthesis: Combine findings into security assessment
|
|
180
|
+
```
|