@imunitic/synapse 0.3.2 → 0.4.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.
@@ -7,6 +7,7 @@
7
7
  "use strict";
8
8
 
9
9
  const { spawnSync } = require("child_process");
10
+ const path = require("path");
10
11
  const { hookPath, platformPackageName } = require("../lib/resolve-binaries.cjs");
11
12
 
12
13
  const bin = hookPath();
@@ -15,5 +16,7 @@ if (!bin) {
15
16
  process.exit(1);
16
17
  }
17
18
 
18
- const result = spawnSync(bin, process.argv.slice(2), { stdio: "inherit" });
19
+ const env = { ...process.env };
20
+ if (!env.SYNAPSE_CONTENT_ROOT) env.SYNAPSE_CONTENT_ROOT = path.join(__dirname, "..");
21
+ const result = spawnSync(bin, process.argv.slice(2), { stdio: "inherit", env });
19
22
  process.exit(result.status ?? 1);
package/bin/synapse.cjs CHANGED
@@ -8,6 +8,7 @@
8
8
  "use strict";
9
9
 
10
10
  const { spawnSync } = require("child_process");
11
+ const path = require("path");
11
12
  const { cliPath, platformPackageName } = require("../lib/resolve-binaries.cjs");
12
13
 
13
14
  const bin = cliPath();
@@ -16,5 +17,7 @@ if (!bin) {
16
17
  process.exit(1);
17
18
  }
18
19
 
19
- const result = spawnSync(bin, process.argv.slice(2), { stdio: "inherit" });
20
+ const env = { ...process.env };
21
+ if (!env.SYNAPSE_CONTENT_ROOT) env.SYNAPSE_CONTENT_ROOT = path.join(__dirname, "..");
22
+ const result = spawnSync(bin, process.argv.slice(2), { stdio: "inherit", env });
20
23
  process.exit(result.status ?? 1);
@@ -177,10 +177,13 @@ so the `## Status` line is the only lifecycle marker that matters. It simply sta
177
177
 
178
178
  ```
179
179
  ---
180
+ schema: vault-design-note/v1
180
181
  title: "{PROJECT} — {Topic}"
181
182
  project: {prefix}
182
- created: "{now}"
183
183
  note_id: {id}
184
+ created: "{now}"
185
+ updated: "{now}"
186
+ tags: [{comma-separated configured tags, or empty}]
184
187
  ---
185
188
 
186
189
  # {PROJECT} — {Topic}
@@ -204,7 +207,13 @@ Discussing | Ready | Reference
204
207
  - {Anything deferred or unresolved}
205
208
  ```
206
209
 
207
- Fetch machine local time for `created` (`date '+%Y-%m-%d %H:%M'`) never infer it.
210
+ Any string scalar that is all digits (e.g. a numeric-looking title) must be quoted: an
211
+ unquoted all-digit value is parsed as an integer, which fails a `type: string` schema
212
+ field. `title`, `project`, `note_id`, and both timestamps are quoted above.
213
+
214
+ Resolve tags through the `synapse-vault` skill's configured vocabulary procedure. Fetch machine
215
+ local time once (`date '+%Y-%m-%d %H:%M:%S %Z'`) — never infer it — and use the exact same value
216
+ for `created` and `updated`.
208
217
 
209
218
  No `Notes`/changelog section — it stays a small, single-conclusion note; there's no long-running
210
219
  edit history here worth tracking separately.
@@ -171,36 +171,64 @@ or supplied directly by a caller like `/synapse-task-note`):
171
171
  characters (`/ : * ? " < > |`) with `-`, collapse repeated whitespace.
172
172
  No timestamp prefix, no project-slug prefix — the filename is just the
173
173
  (sanitized) title.
174
- 2. Fetch machine local time: `date '+%Y-%m-%d %H:%M'` — never use inferred
175
- time. Use this for the `created` frontmatter field.
176
- 3. Build the file content:
174
+ 2. Resolve tags through the `synapse-vault` skill's configured
175
+ `synapse-tag-vocabulary.conf` procedure. Choose only entries already in that vocabulary; an
176
+ empty list is valid when no tag applies, but the `tags` field itself is mandatory.
177
+ 3. Fetch machine local time once: `date '+%Y-%m-%d %H:%M:%S %Z'` — never use inferred
178
+ time. Use the exact same value for `created` and `updated`.
179
+ 4. Build the file content. A bare note needs a concise Summary. A task note needs lead prose and
180
+ at least one real flat checklist item before it can be written; never create an invalid empty
181
+ skeleton and promise to populate it later.
177
182
 
178
183
  **Bare mode:**
179
184
  ```
180
185
  ---
186
+ schema: vault-note/v1
181
187
  title: "{title}"
182
- created: "{now}"
183
188
  note_id: {id}
189
+ created: "{now}"
190
+ updated: "{now}"
191
+ tags: [{comma-separated tags, or empty}]
184
192
  ---
185
193
 
194
+ # {title}
195
+
196
+ ## Summary
197
+
198
+ {essential content}
186
199
  ```
187
200
 
188
201
  **Task mode:**
189
202
  ```
190
203
  ---
204
+ schema: vault-task-note/v1
191
205
  title: "{title}"
192
- created: "{now}"
206
+ project: {prefix}
193
207
  task_id: {task-id}
208
+ created: "{now}"
209
+ updated: "{now}"
210
+ tags: [{comma-separated tags, or empty}]
194
211
  status: TODO
195
- last_updated: "{now}"
196
212
  ---
197
213
 
198
214
  # {title}
199
215
 
216
+ {task description}
217
+
218
+ ## Checklist
219
+
220
+ - [ ] {first implementation step}
221
+
200
222
  ## Notes
201
223
 
224
+ {constraints or context}
202
225
  ```
203
- 4. Write it with `synapse vault-write <path>` (content on stdin). Task mode: path
226
+
227
+ Quote every scalar string value. In particular, a value that is all digits
228
+ (a numeric-looking `title`, or any future numeric id) must be quoted — an
229
+ unquoted all-digit value is parsed as an integer and fails a `type: string`
230
+ schema field.
231
+ 5. Write it with `synapse vault-write <path>` (content on stdin). Task mode: path
204
232
  `tasks/{project}/{filename}.md` (project resolved in "Resolving the
205
233
  project folder" above). Bare mode: path `{category}/{filename}.md`
206
234
  (category resolved above).
@@ -209,7 +237,6 @@ or supplied directly by a caller like `/synapse-task-note`):
209
237
 
210
238
  Report the file path back to the user.
211
239
 
212
- - Bare mode: note that the note is intentionally near-empty.
240
+ - Bare mode: report the Summary that was captured.
213
241
  - Task mode: note the task ID extracted or resolved, and remind the user
214
- to populate the `## Notes` section and checklist before starting work,
215
- per the `synapse-task` skill.
242
+ that future status transitions are managed by the `synapse-task` skill.
@@ -178,9 +178,13 @@ so the `## Status` line is the only lifecycle marker that matters. It simply sta
178
178
 
179
179
  ```
180
180
  ---
181
+ schema: vault-design-note/v1
181
182
  title: "{PROJECT} — {Topic}"
182
183
  project: {prefix}
184
+ note_id: {id}
183
185
  created: "{now}"
186
+ updated: "{now}"
187
+ tags: [{comma-separated configured tags, or empty}]
184
188
  ---
185
189
 
186
190
  # {PROJECT} — {Topic}
@@ -204,7 +208,9 @@ Discussing | Ready | Reference
204
208
  - {Anything deferred or unresolved}
205
209
  ```
206
210
 
207
- Fetch machine local time for `created` (`date '+%Y-%m-%d %H:%M'`) never infer it.
211
+ Resolve tags through the `synapse-vault` skill's configured vocabulary procedure. Fetch machine
212
+ local time once (`date '+%Y-%m-%d %H:%M:%S %Z'`) — never infer it — and use the exact same value
213
+ for `created` and `updated`.
208
214
 
209
215
  No `Notes`/changelog section — it stays a small, single-conclusion note; there's no long-running
210
216
  edit history here worth tracking separately.
@@ -189,36 +189,58 @@ or supplied directly by a caller like the synapse-task-note skill):
189
189
  characters (`/ : * ? " < > |`) with `-`, collapse repeated whitespace.
190
190
  No timestamp prefix, no project-slug prefix — the filename is just the
191
191
  (sanitized) title.
192
- 2. Fetch machine local time: `date '+%Y-%m-%d %H:%M'` — never use inferred
193
- time. Use this for the `created` frontmatter field.
194
- 3. Build the file content:
192
+ 2. Resolve tags through the `synapse-vault` skill's configured
193
+ `synapse-tag-vocabulary.conf` procedure. Choose only configured entries; an empty list is valid
194
+ when no tag applies, but the field itself is mandatory.
195
+ 3. Fetch machine local time once: `date '+%Y-%m-%d %H:%M:%S %Z'` — never use inferred
196
+ time. Use the exact same value for `created` and `updated`.
197
+ 4. Build the file content. Bare notes require a concise Summary. Task notes require lead prose and
198
+ at least one real flat checklist item before the first write.
195
199
 
196
200
  **Bare mode:**
197
201
  ```
198
202
  ---
203
+ schema: vault-note/v1
199
204
  title: "{title}"
200
- created: "{now}"
201
205
  note_id: {id}
206
+ created: "{now}"
207
+ updated: "{now}"
208
+ tags: [{comma-separated tags, or empty}]
202
209
  ---
203
210
 
211
+ # {title}
212
+
213
+ ## Summary
214
+
215
+ {essential content}
204
216
  ```
205
217
 
206
218
  **Task mode:**
207
219
  ```
208
220
  ---
221
+ schema: vault-task-note/v1
209
222
  title: "{title}"
210
- created: "{now}"
223
+ project: {prefix}
211
224
  task_id: {task-id}
225
+ created: "{now}"
226
+ updated: "{now}"
227
+ tags: [{comma-separated tags, or empty}]
212
228
  status: TODO
213
- last_updated: "{now}"
214
229
  ---
215
230
 
216
231
  # {title}
217
232
 
233
+ {task description}
234
+
235
+ ## Checklist
236
+
237
+ - [ ] {first implementation step}
238
+
218
239
  ## Notes
219
240
 
241
+ {constraints or context}
220
242
  ```
221
- 4. Write it with `synapse vault-write <path>` (content on stdin). Task mode: path
243
+ 5. Write it with `synapse vault-write <path>` (content on stdin). Task mode: path
222
244
  `tasks/{project}/{filename}.md` (project resolved in "Resolving the
223
245
  project folder" above). Bare mode: path `{category}/{filename}.md`
224
246
  (category resolved above).
@@ -227,7 +249,6 @@ or supplied directly by a caller like the synapse-task-note skill):
227
249
 
228
250
  Report the file path back to the user.
229
251
 
230
- - Bare mode: note that the note is intentionally near-empty.
252
+ - Bare mode: report the Summary that was captured.
231
253
  - Task mode: note the task ID extracted or resolved, and remind the user
232
- to populate the `## Notes` section and checklist before starting work,
233
- per the `synapse-task` skill.
254
+ that future status transitions are managed by the `synapse-task` skill.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@imunitic/synapse",
3
- "version": "0.3.2",
3
+ "version": "0.4.0",
4
4
  "description": "Memory for Claude Code, Codex CLI, and OpenCode: a durable Obsidian vault plus a per-repo code graph.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -17,14 +17,15 @@
17
17
  "skills",
18
18
  "commands",
19
19
  "harness",
20
+ "schema",
20
21
  "synapse-claude.md",
21
22
  "Index.md.template",
22
23
  "*.conf.template"
23
24
  ],
24
25
  "optionalDependencies": {
25
- "@imunitic/synapse-darwin-arm64": "0.3.2",
26
- "@imunitic/synapse-linux-x64": "0.3.2",
27
- "@imunitic/synapse-linux-arm64": "0.3.2"
26
+ "@imunitic/synapse-darwin-arm64": "0.4.0",
27
+ "@imunitic/synapse-linux-x64": "0.4.0",
28
+ "@imunitic/synapse-linux-arm64": "0.4.0"
28
29
  },
29
30
  "license": "SEE LICENSE IN LICENSE"
30
31
  }
@@ -0,0 +1,95 @@
1
+ # Declared keys are the full v1 spec; enforcement is a deliberate subset — unknown note fields are ignored, never an error.
2
+ schema: synapse-note-schema/v1
3
+ id: vault-design-note/v1
4
+
5
+ frontmatter:
6
+ fields:
7
+ schema:
8
+ type: string
9
+ required: true
10
+ const: vault-design-note/v1
11
+ title:
12
+ type: string
13
+ required: true
14
+ min_length: 1
15
+ project:
16
+ type: string
17
+ required: true
18
+ min_length: 1
19
+ note_id:
20
+ type: string
21
+ required: true
22
+ pattern: '^[a-z][a-z0-9-]*-[0-9]{3,}$'
23
+ mutable: false
24
+ created:
25
+ type: timestamp
26
+ required: true
27
+ format: 'YYYY-MM-dd HH:mm:ss TZ'
28
+ timezone: local
29
+ mutable: false
30
+ updated:
31
+ type: timestamp
32
+ required: true
33
+ format: 'YYYY-MM-dd HH:mm:ss TZ'
34
+ timezone: local
35
+ update_on: [vault-write, vault-patch]
36
+ tags:
37
+ type: list
38
+ required: true
39
+ items: string
40
+
41
+ body:
42
+ h1:
43
+ required: true
44
+ count: 1
45
+ equals: frontmatter.title
46
+ preamble:
47
+ - type: blockquote
48
+ required: false
49
+ position: immediately_after_h1
50
+ pattern: '^> Compiled task: \[\[[^\]]+\]\]$'
51
+ sections:
52
+ - title: Status
53
+ level: 2
54
+ required: true
55
+ content:
56
+ type: string
57
+ enum: [Discussing, Ready, Reference]
58
+ - title: Problem
59
+ level: 2
60
+ required: true
61
+ non_empty: true
62
+ - title: Approach
63
+ level: 2
64
+ required: true
65
+ non_empty: true
66
+ children:
67
+ - title: Alternatives considered
68
+ level: 3
69
+ required: false
70
+ non_empty: true
71
+ - title: Constraints
72
+ level: 2
73
+ required: true
74
+ non_empty: true
75
+ - title: Open Questions
76
+ level: 2
77
+ required: false
78
+ non_empty: true
79
+ section_order: relative
80
+
81
+ checks:
82
+ - equals: [filename.stem, frontmatter.title]
83
+ - unique: frontmatter.note_id
84
+ when: create
85
+ - vocabulary:
86
+ field: frontmatter.project
87
+ source: synapse-projects.conf
88
+ projection: values
89
+ - vocabulary:
90
+ field: frontmatter.tags
91
+ source: synapse-tag-vocabulary.conf
92
+ - not_before: [frontmatter.updated, frontmatter.created]
93
+ - equals:
94
+ values: [frontmatter.created, frontmatter.updated]
95
+ when: create
@@ -0,0 +1,67 @@
1
+ # Declared keys are the full v1 spec; enforcement is a deliberate subset — unknown note fields are ignored, never an error.
2
+ schema: synapse-note-schema/v1
3
+ id: vault-note/v1
4
+
5
+ frontmatter:
6
+ fields:
7
+ schema:
8
+ type: string
9
+ required: true
10
+ const: vault-note/v1
11
+ title:
12
+ type: string
13
+ required: true
14
+ min_length: 1
15
+ note_id:
16
+ type: string
17
+ required: true
18
+ pattern: '^[a-z][a-z0-9-]*-[0-9]{3,}$'
19
+ mutable: false
20
+ created:
21
+ type: timestamp
22
+ required: true
23
+ format: 'YYYY-MM-dd HH:mm:ss TZ'
24
+ timezone: local
25
+ mutable: false
26
+ updated:
27
+ type: timestamp
28
+ required: true
29
+ format: 'YYYY-MM-dd HH:mm:ss TZ'
30
+ timezone: local
31
+ update_on: [vault-write, vault-patch]
32
+ tags:
33
+ type: list
34
+ required: true
35
+ items: string
36
+
37
+ body:
38
+ h1:
39
+ required: true
40
+ count: 1
41
+ equals: frontmatter.title
42
+ sections:
43
+ - title: Summary
44
+ level: 2
45
+ required: true
46
+ - title: Details
47
+ level: 2
48
+ required: false
49
+ - title: Sources
50
+ level: 2
51
+ required: false
52
+ - title: Open Questions
53
+ level: 2
54
+ required: false
55
+ section_order: relative
56
+
57
+ checks:
58
+ - equals: [filename.stem, frontmatter.title]
59
+ - unique: frontmatter.note_id
60
+ when: create
61
+ - vocabulary:
62
+ field: frontmatter.tags
63
+ source: synapse-tag-vocabulary.conf
64
+ - not_before: [frontmatter.updated, frontmatter.created]
65
+ - equals:
66
+ values: [frontmatter.created, frontmatter.updated]
67
+ when: create
@@ -0,0 +1,93 @@
1
+ # Declared keys are the full v1 spec; enforcement is a deliberate subset — unknown note fields are ignored, never an error.
2
+ schema: synapse-note-schema/v1
3
+ id: vault-task-note/v1
4
+
5
+ frontmatter:
6
+ fields:
7
+ schema:
8
+ type: string
9
+ required: true
10
+ const: vault-task-note/v1
11
+ title:
12
+ type: string
13
+ required: true
14
+ min_length: 1
15
+ project:
16
+ type: string
17
+ required: true
18
+ min_length: 1
19
+ task_id:
20
+ type: string
21
+ required: true
22
+ pattern: '^[a-z][a-z0-9-]*-[0-9]{3,}$'
23
+ mutable: false
24
+ created:
25
+ type: timestamp
26
+ required: true
27
+ format: 'YYYY-MM-dd HH:mm:ss TZ'
28
+ timezone: local
29
+ mutable: false
30
+ updated:
31
+ type: timestamp
32
+ required: true
33
+ format: 'YYYY-MM-dd HH:mm:ss TZ'
34
+ timezone: local
35
+ update_on: [vault-write, vault-patch]
36
+ tags:
37
+ type: list
38
+ required: true
39
+ items: string
40
+ status:
41
+ type: string
42
+ required: true
43
+ enum: [TODO, IN-PROGRESS, REVIEW, DONE, CANCELED]
44
+
45
+ body:
46
+ h1:
47
+ required: true
48
+ count: 1
49
+ equals: frontmatter.title
50
+ lead:
51
+ type: prose
52
+ required: true
53
+ position: before_sections
54
+ checklist:
55
+ required: true
56
+ min_items: 1
57
+ position: before_sections
58
+ nested_items: false
59
+ allowed_children: [paragraph, fenced_code]
60
+ sections:
61
+ - title: Checklist
62
+ level: 2
63
+ required: true
64
+ - title: Notes
65
+ level: 2
66
+ required: false
67
+ max_occurs: 1
68
+ children:
69
+ - level: 3
70
+ required: false
71
+ repeatable: true
72
+ title_pattern: '^[0-9]{4}-[0-9]{2}-[0-9]{2} — .+$'
73
+ section_order: relative
74
+
75
+ checks:
76
+ - equals: [filename.stem, frontmatter.title]
77
+ - unique: frontmatter.task_id
78
+ when: create
79
+ - vocabulary:
80
+ field: frontmatter.project
81
+ source: synapse-projects.conf
82
+ projection: values
83
+ - vocabulary:
84
+ field: frontmatter.tags
85
+ source: synapse-tag-vocabulary.conf
86
+ - not_before: [frontmatter.updated, frontmatter.created]
87
+ - equals:
88
+ values: [frontmatter.created, frontmatter.updated]
89
+ when: create
90
+ - const:
91
+ field: frontmatter.status
92
+ value: TODO
93
+ when: create
@@ -21,12 +21,12 @@ Invoke this skill **automatically** in two situations:
21
21
 
22
22
  1. **Starting work on a task** — as soon as the user confirms work is
23
23
  beginning, before writing any code. Set `status: IN-PROGRESS` and update
24
- `last_updated`. No notes needed at this point.
24
+ `updated`. No notes needed at this point.
25
25
 
26
26
  2. **Finishing work on a task** — after all phases are committed and
27
27
  the task note's checklist has been updated. Set `status: REVIEW` (if all
28
28
  items checked) or `status: IN-PROGRESS` (if any remain), update
29
- `last_updated`, and append an implementation summary to the `## Notes`
29
+ `updated`, and append an implementation summary to the `## Notes`
30
30
  section.
31
31
 
32
32
  Never wait to be asked — apply this skill proactively at both transitions.
@@ -35,7 +35,7 @@ Never wait to be asked — apply this skill proactively at both transitions.
35
35
 
36
36
  - Updates the `status:` frontmatter field (`TODO` → `IN-PROGRESS` or
37
37
  `REVIEW`).
38
- - Updates `last_updated` in frontmatter.
38
+ - Updates `updated` in frontmatter.
39
39
  - At task completion: appends concise implementation bullets to the
40
40
  existing `## Notes` section (or creates one if none exists).
41
41
 
@@ -60,11 +60,11 @@ into `status:` either — always go through this skill, which caps at
60
60
  2. Inspect its checklist items (`- [ ]` / `- [x]`).
61
61
  3. Determine the new `status:` value: `IN-PROGRESS` if any unchecked,
62
62
  `REVIEW` if all checked.
63
- 4. Fetch machine local time: `date '+%Y-%m-%d %H:%M'` — never use inferred
63
+ 4. Fetch machine local time: `date '+%Y-%m-%d %H:%M:%S %Z'` — never use inferred
64
64
  time.
65
- 5. Update `status:` and `last_updated:` with two `synapse frontmatter set`
65
+ 5. Update `status:` and `updated:` with two `synapse frontmatter set`
66
66
  calls, one per field: `synapse frontmatter set <path> status <value>`
67
- then `synapse frontmatter set <path> last_updated "{now}"`. Each call
67
+ then `synapse frontmatter set <path> updated "{now}"`. Each call
68
68
  changes exactly that one line and nothing else, entirely inside the
69
69
  compiled binary — the note's body never enters your context at all.
70
70
  When the command isn't available, fall back to **read-modify-write**:
@@ -94,12 +94,14 @@ The vault-patch hazards below are the task-note-specific instance of a general r
94
94
  `synapse-vault` skill carries the full list (H1 replace, nested heading paths, frontmatter) for
95
95
  every note, not just task notes.
96
96
 
97
- **Do not use `vault-patch --heading "{H1 title}" --replace` to edit checklist items.** A top-level
98
- heading's own section extends through *all* nested subheadings (including `## Notes`), not just
99
- the leading paragraph/checklist directly under it a replace there silently deletes everything
100
- past the checklist, including the Notes section. To check off checklist items,
101
- instead `synapse vault-read` the full file, edit the `- [ ]` `- [x]` lines in the
102
- returned content, and `synapse vault-write` the whole file back.
97
+ **Edit checklist items through the `Checklist` heading, never the H1.** With `## Checklist` as its
98
+ own H2 a sibling of `## Notes` `vault-patch --heading "{H1 title}::Checklist" --replace`
99
+ (supplying the full new checklist) is the correct tool for a whole-checklist edit and leaves
100
+ `## Notes` untouched. Never target the top-level H1 (`--heading "{H1 title}"`): its section extends
101
+ through *all* nested subheadings (including `## Notes`), so a replace there silently deletes
102
+ everything past the checklist. To flip a single `- [ ]` → `- [x]` in place, `synapse vault-read`
103
+ the full file, edit that one line, and `synapse vault-write` the whole file back — there is no
104
+ per-item toggle yet.
103
105
 
104
106
  ## Notes format
105
107
 
@@ -116,18 +118,26 @@ level. Append to the last existing notes section.
116
118
  ## Task file structure
117
119
 
118
120
  Each task note, in any project, has **exactly one top-level heading** (the
119
- task itself, `# {title}`). Implementation steps go as `- [ ]` checklist items
120
- **under that heading**, not as additional headings. Do not create `##
121
- Step` sub-headings for implementation steps.
121
+ task itself, `# {title}`). Under it: lead prose describing the task, then a
122
+ required `## Checklist` heading holding the `- [ ]` implementation items,
123
+ then a `## Notes` section. Do not create `## Step` sub-headings for
124
+ implementation steps, and do not put checklist items directly under the H1.
122
125
 
123
126
  Correct structure:
124
127
  ```md
125
128
  # Implement something
126
129
 
127
130
  Description of the task.
131
+
132
+ ## Checklist
133
+
128
134
  - [ ] Step one
129
135
  - [ ] Step two
130
136
  - [ ] Step three
137
+
138
+ ## Notes
139
+
140
+ {constraints or context}
131
141
  ```
132
142
 
133
143
  Wrong structure (do not do this):