@iceinvein/agent-skills 0.1.39 → 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/README.md +18 -2
- package/dist/cli/index.js +105 -28
- package/package.json +1 -1
- package/skills/index.json +14 -2
- package/skills/magpie/README.md +2 -1
- package/skills/magpie/SKILL.md +138 -45
- package/skills/magpie/bin/magpie.ts +43 -0
- package/skills/magpie/fixtures/example-pr/brief.json +18 -0
- package/skills/magpie/fixtures/fake-gh-nodiff.sh +38 -0
- package/skills/magpie/fixtures/fake-gh.sh +14 -0
- package/skills/magpie/package.json +1 -1
- package/skills/magpie/references/peer-review.md +7 -2
- package/skills/magpie/references/scout.md +90 -0
- package/skills/magpie/references/specialists.md +117 -6
- package/skills/magpie/scripts/__tests__/cli.test.ts +101 -1
- package/skills/magpie/scripts/__tests__/dedupe-cmd.test.ts +187 -0
- package/skills/magpie/scripts/__tests__/diff-chunks.test.ts +51 -0
- package/skills/magpie/scripts/__tests__/filter-diff-preservation.test.ts +54 -0
- package/skills/magpie/scripts/__tests__/findings-files.test.ts +35 -0
- package/skills/magpie/scripts/__tests__/gh.test.ts +90 -0
- package/skills/magpie/scripts/__tests__/git-diff.test.ts +83 -0
- package/skills/magpie/scripts/__tests__/helpers/git-fixture.ts +47 -0
- package/skills/magpie/scripts/__tests__/path-filter.test.ts +27 -0
- package/skills/magpie/scripts/__tests__/preview-cmd.test.ts +16 -0
- package/skills/magpie/scripts/__tests__/refresh.test.ts +45 -0
- package/skills/magpie/scripts/__tests__/render-cmd.test.ts +173 -1
- package/skills/magpie/scripts/__tests__/render-findings.test.ts +151 -1
- package/skills/magpie/scripts/__tests__/render-progress.test.ts +42 -0
- package/skills/magpie/scripts/__tests__/setup-cmd.test.ts +83 -1
- package/skills/magpie/scripts/__tests__/shard.test.ts +165 -0
- package/skills/magpie/scripts/__tests__/skill-lint.test.ts +207 -7
- package/skills/magpie/scripts/__tests__/types.test.ts +47 -0
- package/skills/magpie/scripts/dedupe-cmd.ts +58 -3
- package/skills/magpie/scripts/diff-chunks.ts +28 -0
- package/skills/magpie/scripts/findings-files.ts +32 -0
- package/skills/magpie/scripts/gh.ts +68 -14
- package/skills/magpie/scripts/git-diff.ts +111 -0
- package/skills/magpie/scripts/path-filter.ts +9 -5
- package/skills/magpie/scripts/preview-cmd.ts +11 -1
- package/skills/magpie/scripts/refresh.ts +32 -3
- package/skills/magpie/scripts/render-cmd.ts +35 -11
- package/skills/magpie/scripts/render-findings.ts +78 -2
- package/skills/magpie/scripts/render-progress.ts +6 -1
- package/skills/magpie/scripts/setup-cmd.ts +38 -1
- package/skills/magpie/scripts/shard.ts +171 -0
- package/skills/magpie/scripts/status-cmd.ts +7 -4
- package/skills/magpie/scripts/types.ts +50 -0
- package/skills/magpie/skill.json +2 -2
- package/skills/magpie/templates/styles.css +66 -0
- package/skills/migrate/README.md +194 -0
- package/skills/migrate/SKILL.md +197 -0
- package/skills/migrate/bin/migrate +15 -0
- package/skills/migrate/bin/migrate.ts +309 -0
- package/skills/migrate/biome.json +35 -0
- package/skills/migrate/bun.lock +24 -0
- package/skills/migrate/docs/architecture.md +294 -0
- package/skills/migrate/docs/reference.md +590 -0
- package/skills/migrate/fixtures/tiny-express/GROUND-TRUTH.md +39 -0
- package/skills/migrate/fixtures/tiny-express/app.js +29 -0
- package/skills/migrate/fixtures/tiny-express/cron.js +6 -0
- package/skills/migrate/fixtures/tiny-express/reports/daily-users.json +6 -0
- package/skills/migrate/fixtures/tiny-express/schema.sql +12 -0
- package/skills/migrate/fixtures/tiny-express/settings.json +4 -0
- package/skills/migrate/fixtures/tiny-express/views/users.html +9 -0
- package/skills/migrate/fixtures/tiny-webforms/Controllers/UsersController.cs +68 -0
- package/skills/migrate/fixtures/tiny-webforms/Default.aspx +7 -0
- package/skills/migrate/fixtures/tiny-webforms/Default.aspx.cs +14 -0
- package/skills/migrate/fixtures/tiny-webforms/GROUND-TRUTH.md +50 -0
- package/skills/migrate/fixtures/tiny-webforms/Integrations/BillingClient.cs +16 -0
- package/skills/migrate/fixtures/tiny-webforms/Jobs/NightlyDigestJob.cs +33 -0
- package/skills/migrate/fixtures/tiny-webforms/Reports/DailyUsers.rdl +11 -0
- package/skills/migrate/fixtures/tiny-webforms/Schema.sql +12 -0
- package/skills/migrate/fixtures/tiny-webforms/Site.master +16 -0
- package/skills/migrate/fixtures/tiny-webforms/Users.aspx +8 -0
- package/skills/migrate/fixtures/tiny-webforms/Users.aspx.cs +14 -0
- package/skills/migrate/fixtures/tiny-webforms/web.config +10 -0
- package/skills/migrate/install.sh +68 -0
- package/skills/migrate/package.json +17 -0
- package/skills/migrate/references/phases/enumerate.md +291 -0
- package/skills/migrate/references/phases/extract.md +652 -0
- package/skills/migrate/references/phases/parity.md +275 -0
- package/skills/migrate/references/phases/probe.md +135 -0
- package/skills/migrate/references/phases/queue.md +242 -0
- package/skills/migrate/references/phases/seam.md +416 -0
- package/skills/migrate/references/recipes/README.md +116 -0
- package/skills/migrate/references/recipes/aspnet.md +287 -0
- package/skills/migrate/references/run-ops.md +280 -0
- package/skills/migrate/scripts/__tests__/census.test.ts +775 -0
- package/skills/migrate/scripts/__tests__/check.test.ts +458 -0
- package/skills/migrate/scripts/__tests__/citations.test.ts +156 -0
- package/skills/migrate/scripts/__tests__/cli.test.ts +183 -0
- package/skills/migrate/scripts/__tests__/concurrency.test.ts +164 -0
- package/skills/migrate/scripts/__tests__/config.test.ts +112 -0
- package/skills/migrate/scripts/__tests__/e2e-express.test.ts +1093 -0
- package/skills/migrate/scripts/__tests__/e2e-webforms.test.ts +1276 -0
- package/skills/migrate/scripts/__tests__/e2e.test.ts +320 -0
- package/skills/migrate/scripts/__tests__/ids.test.ts +38 -0
- package/skills/migrate/scripts/__tests__/import.test.ts +155 -0
- package/skills/migrate/scripts/__tests__/init.test.ts +192 -0
- package/skills/migrate/scripts/__tests__/leaks.test.ts +176 -0
- package/skills/migrate/scripts/__tests__/lock.test.ts +183 -0
- package/skills/migrate/scripts/__tests__/paths.test.ts +129 -0
- package/skills/migrate/scripts/__tests__/phase-cmd.test.ts +151 -0
- package/skills/migrate/scripts/__tests__/phases.test.ts +70 -0
- package/skills/migrate/scripts/__tests__/queue.test.ts +475 -0
- package/skills/migrate/scripts/__tests__/report.test.ts +150 -0
- package/skills/migrate/scripts/__tests__/run-state.test.ts +136 -0
- package/skills/migrate/scripts/__tests__/status-reset.test.ts +318 -0
- package/skills/migrate/scripts/__tests__/store.test.ts +132 -0
- package/skills/migrate/scripts/__tests__/validate.test.ts +54 -0
- package/skills/migrate/scripts/census-cmd.ts +109 -0
- package/skills/migrate/scripts/census.ts +342 -0
- package/skills/migrate/scripts/check-cmd.ts +24 -0
- package/skills/migrate/scripts/check.ts +376 -0
- package/skills/migrate/scripts/citations.ts +92 -0
- package/skills/migrate/scripts/config.ts +237 -0
- package/skills/migrate/scripts/ids.ts +31 -0
- package/skills/migrate/scripts/import-cmd.ts +141 -0
- package/skills/migrate/scripts/init-cmd.ts +118 -0
- package/skills/migrate/scripts/leaks.ts +184 -0
- package/skills/migrate/scripts/lock.ts +188 -0
- package/skills/migrate/scripts/paths.ts +103 -0
- package/skills/migrate/scripts/phase-cmd.ts +63 -0
- package/skills/migrate/scripts/phases.ts +113 -0
- package/skills/migrate/scripts/queue-cmd.ts +98 -0
- package/skills/migrate/scripts/queue.ts +258 -0
- package/skills/migrate/scripts/report-cmd.ts +47 -0
- package/skills/migrate/scripts/report.ts +131 -0
- package/skills/migrate/scripts/reset-cmd.ts +120 -0
- package/skills/migrate/scripts/status-cmd.ts +52 -0
- package/skills/migrate/scripts/store.ts +159 -0
- package/skills/migrate/scripts/types.ts +137 -0
- package/skills/migrate/scripts/validate.ts +221 -0
- package/skills/migrate/skill.json +33 -0
- package/skills/migrate/templates/config.toml +27 -0
- package/skills/migrate/templates/queue-item.md +17 -0
- package/skills/migrate/tsconfig.json +18 -0
- package/skills/migrate/uninstall.sh +31 -0
- package/skills/sluice/SKILL.md +82 -0
- package/skills/sluice/references/deep-channel.md +94 -0
- package/skills/sluice/references/finish.md +35 -0
- package/skills/sluice/references/intent.md +29 -0
- package/skills/sluice/references/review.md +42 -0
- package/skills/sluice/references/root-cause.md +38 -0
- package/skills/sluice/references/show-or-say.md +36 -0
- package/skills/sluice/references/test-first.md +35 -0
- package/skills/sluice/references/verify.md +26 -0
- package/skills/sluice/skill.json +32 -0
|
@@ -0,0 +1,652 @@
|
|
|
1
|
+
# Phase 3: Extract
|
|
2
|
+
|
|
3
|
+
## Purpose
|
|
4
|
+
|
|
5
|
+
Mine functional requirements out of every capability's elements, keeping a
|
|
6
|
+
citation trail back to the evidence, and give every element a terminal
|
|
7
|
+
disposition. Exit condition: every capability has been mined, every element
|
|
8
|
+
carries `disposition.kind` of `mapped` or `out-of-scope` (never
|
|
9
|
+
`unaccounted`), every declared closer in `[closers].set` has one `closer`
|
|
10
|
+
census record, and `migrate phase extract --status done` has run.
|
|
11
|
+
|
|
12
|
+
## Inputs
|
|
13
|
+
|
|
14
|
+
- `config.toml`: `[closers].set` (the closers you owe a census record,
|
|
15
|
+
independent of how many you happen to find), `source.stack` (whether a
|
|
16
|
+
recipe supplies attribute directions the way it supplies lens directions
|
|
17
|
+
in enumerate; contract-only applies here exactly as it does there).
|
|
18
|
+
- The store: `capabilities.jsonl` (the fanout unit; every element inside one
|
|
19
|
+
capability's `elements` array is this pass's scope), `elements.jsonl`
|
|
20
|
+
(every row still `unaccounted` after enumerate is what you are mining),
|
|
21
|
+
`requirements.jsonl` (what already exists, for resuming without
|
|
22
|
+
re-mining), `phases.json` (committed batches, for resuming).
|
|
23
|
+
- The source checkout (read-only): the citation gate resolves every `src`
|
|
24
|
+
citation against it.
|
|
25
|
+
|
|
26
|
+
## Procedure
|
|
27
|
+
|
|
28
|
+
**Extract's contract, stated once.** For each capability, read every
|
|
29
|
+
element it owns, decide what functional requirement that element's evidence
|
|
30
|
+
actually supports, write one requirement per distinct behavior with at
|
|
31
|
+
least one citation, and write back a terminal disposition onto every
|
|
32
|
+
element the requirement accounts for. Nothing here is optional: an element
|
|
33
|
+
extract does not dispose of stays `unaccounted` forever, since disposition
|
|
34
|
+
has exactly one writer (below), and a requirement with no citation cannot
|
|
35
|
+
be imported at all.
|
|
36
|
+
|
|
37
|
+
1. **Fan out one agent per capability.** A capability is the unit; an agent
|
|
38
|
+
mining `user-management` never needs to know what `invoicing` looks
|
|
39
|
+
like. See `references/run-ops.md` for dispatch and checkpoint mechanics;
|
|
40
|
+
this manual does not restate them.
|
|
41
|
+
|
|
42
|
+
2. **Confidence tiers, and the evidence trust order that sets them.** Every
|
|
43
|
+
requirement's `confidence.kind` is `confirmed`, `inferred`, or `queued`.
|
|
44
|
+
When two lenses' evidence disagree about what the source actually does,
|
|
45
|
+
trust them in this order, strongest first: **`runtime` > `code` > `nav`
|
|
46
|
+
> `docs`**. Runtime is what the system actually did when observed;
|
|
47
|
+
`code` is the logic that decides what it does, but a path can be dead or
|
|
48
|
+
never actually reached; `nav` shows what is exposed, not what it does
|
|
49
|
+
once reached; `docs` is aspirational and goes stale fastest of the four.
|
|
50
|
+
Assign the tier from what the trusted evidence actually shows, not from
|
|
51
|
+
how confident you feel: **confirmed** when the highest-trust evidence
|
|
52
|
+
available is unambiguous and nothing contradicts it; **inferred** when
|
|
53
|
+
the evidence shows part of the behavior but a piece is genuinely
|
|
54
|
+
unobservable from here (a call to something outside the citable source);
|
|
55
|
+
**queued** when the evidence conflicts or is too thin to call either way
|
|
56
|
+
and an owner has to.
|
|
57
|
+
|
|
58
|
+
3. **Origin tagging is a second, independent judgment, not a restatement of
|
|
59
|
+
confidence.** `origin` is `intended` or `accidental-candidate`.
|
|
60
|
+
Confidence asks "how sure am I this is what happens"; origin asks
|
|
61
|
+
"does this read as deliberate, or as something nobody meant to build
|
|
62
|
+
this way." A requirement can be fully confirmed and still
|
|
63
|
+
accidental-candidate: the evidence can be completely unambiguous about
|
|
64
|
+
*what* happens while leaving open *whether it should*.
|
|
65
|
+
|
|
66
|
+
A worked example, run against a real store. `Controllers/AuthController.cs`
|
|
67
|
+
has a `Login` method that validates credentials, a `GetUsers` method with
|
|
68
|
+
no `[Authorize]` attribute and no auth check of any kind, and a
|
|
69
|
+
`ResetPassword` method whose comment says only "no test coverage in the
|
|
70
|
+
legacy system; behavior beyond 'a link is emailed' is not observable from
|
|
71
|
+
this method alone":
|
|
72
|
+
|
|
73
|
+
```json
|
|
74
|
+
[
|
|
75
|
+
{
|
|
76
|
+
"id": "UM-001",
|
|
77
|
+
"cap": "user-management",
|
|
78
|
+
"requirement": "User must provide a valid email and password to log in",
|
|
79
|
+
"actors": "User",
|
|
80
|
+
"objects": "Credentials",
|
|
81
|
+
"rules": "Email must contain @; password minimum 8 characters",
|
|
82
|
+
"origin": "intended",
|
|
83
|
+
"confidence": { "kind": "confirmed" },
|
|
84
|
+
"citations": [
|
|
85
|
+
{ "kind": "ledger", "id": "route-post-api-login" },
|
|
86
|
+
{ "kind": "src", "path": "Controllers/AuthController.cs", "lines": [9, 25] }
|
|
87
|
+
],
|
|
88
|
+
"parity": null
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
"id": "UM-002",
|
|
92
|
+
"cap": "user-management",
|
|
93
|
+
"requirement": "Any caller can list every user, with no authentication check",
|
|
94
|
+
"actors": "Any caller",
|
|
95
|
+
"objects": "User list",
|
|
96
|
+
"rules": "-",
|
|
97
|
+
"origin": "accidental-candidate",
|
|
98
|
+
"confidence": { "kind": "confirmed" },
|
|
99
|
+
"citations": [
|
|
100
|
+
{ "kind": "ledger", "id": "route-get-api-users" },
|
|
101
|
+
{ "kind": "src", "path": "Controllers/AuthController.cs", "lines": [27, 32] }
|
|
102
|
+
],
|
|
103
|
+
"parity": null
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
"id": "UM-003",
|
|
107
|
+
"cap": "user-management",
|
|
108
|
+
"requirement": "User can request a password reset link by email without the response revealing whether the account exists",
|
|
109
|
+
"actors": "User",
|
|
110
|
+
"objects": "Reset token",
|
|
111
|
+
"rules": "Response is identical whether or not the account exists",
|
|
112
|
+
"origin": "intended",
|
|
113
|
+
"confidence": { "kind": "inferred" },
|
|
114
|
+
"citations": [
|
|
115
|
+
{ "kind": "ledger", "id": "route-post-api-password-reset" },
|
|
116
|
+
{ "kind": "src", "path": "Controllers/AuthController.cs", "lines": [34, 45] }
|
|
117
|
+
],
|
|
118
|
+
"parity": null
|
|
119
|
+
}
|
|
120
|
+
]
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
(this is the batch file's `rows` array; the envelope carrying `batch` and
|
|
124
|
+
`phase` around it is elided here since import-cmd.ts's own shape is
|
|
125
|
+
already covered above.)
|
|
126
|
+
|
|
127
|
+
`migrate import reqs batch.json` accepts all three in one call and prints
|
|
128
|
+
`import reqs: 3 added, 0 updated, batch b-reqs-um-001`. UM-002's
|
|
129
|
+
confidence is `confirmed` because the missing auth check is not in doubt;
|
|
130
|
+
its origin is `accidental-candidate` because nothing in the source
|
|
131
|
+
suggests leaving every user's data open to any caller was ever a decision
|
|
132
|
+
anyone made. UM-003 is where the evidence trust order actually bites:
|
|
133
|
+
`code` confirms a link is sent and that a missing account does not change
|
|
134
|
+
the response, but nothing citable in this source shows what happens once
|
|
135
|
+
a token is submitted, so that piece is `inferred`, not `confirmed`, no
|
|
136
|
+
matter how confident the wording sounds. `parity` is `null` on all three
|
|
137
|
+
on purpose: assigning an oracle is phase 4's job, not this one's, and the
|
|
138
|
+
parity gate exempts nothing at `confirmed` or `inferred`, only at
|
|
139
|
+
`queued` (parity.md covers why).
|
|
140
|
+
|
|
141
|
+
### Mandatory citations, and the citation gate
|
|
142
|
+
|
|
143
|
+
**At least one citation is required on every requirement; `migrate import
|
|
144
|
+
reqs` refuses one with none.** This was always the rule; what changed is
|
|
145
|
+
that a fabricated `src` citation now fails a gate instead of merely
|
|
146
|
+
breaking one. `citations` runs by default (`--no-citations` opts out) and
|
|
147
|
+
resolves every `src` citation against the read-only source checkout,
|
|
148
|
+
line ranges included.
|
|
149
|
+
|
|
150
|
+
A worked failure, run on a disposable copy of the store so this defective
|
|
151
|
+
row never enters the running example: a requirement citing a controller
|
|
152
|
+
that was never part of the checkout.
|
|
153
|
+
|
|
154
|
+
```json
|
|
155
|
+
{ "kind": "src", "path": "Controllers/SessionController.cs", "lines": [10, 20] }
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
`migrate import reqs` accepts the row (the importer only checks shape, not
|
|
159
|
+
existence), but `migrate check` reports it under its own gate, by name:
|
|
160
|
+
|
|
161
|
+
```
|
|
162
|
+
citations:
|
|
163
|
+
UM-004 cites Controllers/SessionController.cs, which does not exist in the source tree
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
A citation with an inverted range (`start > end`) or a range past the
|
|
167
|
+
file's actual line count fails the same gate the same way, each with its
|
|
168
|
+
own message naming the requirement and the exact problem.
|
|
169
|
+
|
|
170
|
+
### Writing the disposition back
|
|
171
|
+
|
|
172
|
+
**Past enumerate, the elements batch you import here is the only writer of
|
|
173
|
+
a *resolved* `disposition`.** Nothing about writing a requirement changes an
|
|
174
|
+
element's disposition by itself; every element the requirement accounts
|
|
175
|
+
for needs its own row in an elements batch, disposition set to `mapped`
|
|
176
|
+
with the requirement's id, or to `out-of-scope` with a queue id if it is
|
|
177
|
+
being carried forward unmapped on purpose. This is the write-back
|
|
178
|
+
`SKILL.md` calls out as load-bearing: a review found the walkthrough could
|
|
179
|
+
not clear its own coverage gate without this second import, because
|
|
180
|
+
nothing else moves an element toward a terminal disposition once enumerate
|
|
181
|
+
ends. (`migrate reset --phase extract` also writes this field, but only
|
|
182
|
+
back to `unaccounted`, undoing the phase rather than progressing it.)
|
|
183
|
+
|
|
184
|
+
A worked example, run against the same store, four elements mapped and one
|
|
185
|
+
carried out of scope:
|
|
186
|
+
|
|
187
|
+
```json
|
|
188
|
+
[
|
|
189
|
+
{ "id": "route-post-api-login", "surface": "routes", "element": "POST /api/login",
|
|
190
|
+
"found_by": ["code", "nav"], "disposition": { "kind": "mapped", "fr": "UM-001" },
|
|
191
|
+
"refs": [{ "kind": "src", "path": "Controllers/AuthController.cs", "lines": [9, 25] }],
|
|
192
|
+
"lens": "code", "notes": "" },
|
|
193
|
+
{ "id": "route-get-legacy-admin-tool", "surface": "routes", "element": "GET /legacy-admin-tool",
|
|
194
|
+
"found_by": ["code"], "disposition": { "kind": "out-of-scope", "queue": "q-legacy-admin-tool" },
|
|
195
|
+
"refs": [], "lens": "code", "notes": "not linked from nav; found only by grepping controller attributes" }
|
|
196
|
+
]
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
(again, the `rows` array; the other three rows in the real batch are the
|
|
200
|
+
same shape and are omitted here for space: `route-get-api-users` mapped to
|
|
201
|
+
`UM-002`, `route-post-api-password-reset` mapped to `UM-003`, and
|
|
202
|
+
`table-users` mapped to `UM-001`, the table backing the credentials check
|
|
203
|
+
UM-001 describes.)
|
|
204
|
+
|
|
205
|
+
`migrate import elements batch.json` accepts the full batch and prints
|
|
206
|
+
`import elements: 0 added, 5 updated, batch b-elements-disposition-001`
|
|
207
|
+
(upsert by id, same mechanic as any other re-import). Coverage moves from
|
|
208
|
+
`0/5 mapped, 0 out-of-scope, 5 unaccounted` before this batch to `4/5
|
|
209
|
+
mapped, 1 out-of-scope, 0 unaccounted` after it.
|
|
210
|
+
|
|
211
|
+
**An `out-of-scope` disposition's queue id is checked for existence by the
|
|
212
|
+
refs gate, and file it before you rely on that in a check, not after.**
|
|
213
|
+
`migrate check` reads the current store every time it runs; a queue id an
|
|
214
|
+
element names but that has no file yet is a real, present violation the
|
|
215
|
+
moment anything checks, not a future one. File it in the same pass:
|
|
216
|
+
|
|
217
|
+
```markdown
|
|
218
|
+
---
|
|
219
|
+
id: q-legacy-admin-tool
|
|
220
|
+
severity: minor
|
|
221
|
+
status: open
|
|
222
|
+
---
|
|
223
|
+
|
|
224
|
+
## Evidence
|
|
225
|
+
|
|
226
|
+
`GET /legacy-admin-tool` was found by the `code` lens (grepping controller
|
|
227
|
+
attributes) but not by `nav` (it has no menu entry, no link anywhere in
|
|
228
|
+
Startup.cs's route registrations). No FR describes it.
|
|
229
|
+
|
|
230
|
+
## Options
|
|
231
|
+
|
|
232
|
+
(a) Carry it forward as-is. (b) Mark it out of scope as dead code. (c) Ask
|
|
233
|
+
the operator whether anything still calls it.
|
|
234
|
+
|
|
235
|
+
## Recommendation
|
|
236
|
+
|
|
237
|
+
Recommend (b); an endpoint with no nav entry and no citation anywhere else
|
|
238
|
+
in the source reads as abandoned, not hidden-but-load-bearing.
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
`migrate queue add q-legacy-admin-tool.md` accepts this and prints `queue
|
|
242
|
+
add: q-legacy-admin-tool [minor]` (queue.md covers the grammar this file
|
|
243
|
+
must satisfy). Only after this does the coverage line above hold up against
|
|
244
|
+
a real `check`, not merely against the batch import that produced it.
|
|
245
|
+
|
|
246
|
+
### The business-rule lens: rule-sweep
|
|
247
|
+
|
|
248
|
+
**A `rule-sweep` census records a deliberate search, per capability, for
|
|
249
|
+
code-enforced rules that no CRUD-shaped requirement already captured.**
|
|
250
|
+
Balance: `found == as_requirements + queued.length`. It has no `directions`
|
|
251
|
+
field and no bound: it is a sweep, not a two-direction enumeration, so
|
|
252
|
+
nothing here asks it to dedupe across independent counting methods the way
|
|
253
|
+
a lens or an attribute census does.
|
|
254
|
+
|
|
255
|
+
A worked example: `AuthController.cs:8` carries `// TODO: lock the account
|
|
256
|
+
after 5 failed attempts; not implemented yet` directly above `Login`. The
|
|
257
|
+
sweep found it; it cannot become a requirement describing current behavior
|
|
258
|
+
because it is explicitly not implemented, so it goes to the queue instead.
|
|
259
|
+
|
|
260
|
+
```json
|
|
261
|
+
{
|
|
262
|
+
"kind": "rule-sweep",
|
|
263
|
+
"subject": "user-management",
|
|
264
|
+
"phase": "extract",
|
|
265
|
+
"probes": 3,
|
|
266
|
+
"found": 1,
|
|
267
|
+
"as_requirements": 0,
|
|
268
|
+
"queued": ["q-account-lockout-scope"],
|
|
269
|
+
"batch": "b-rules-um-001"
|
|
270
|
+
}
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
`migrate census rule-sweep.json` accepts this and prints `census: recorded
|
|
274
|
+
rule-sweep:user-management`. An imbalanced record is rejected with the
|
|
275
|
+
specific numbers named: submitting `found: 2` against the same
|
|
276
|
+
`as_requirements`/`queued` reports `rule-sweep census for user-management
|
|
277
|
+
does not balance: found 2 but as_requirements 0 + queued 1 = 1`, run
|
|
278
|
+
against a real store.
|
|
279
|
+
|
|
280
|
+
File the queue item this record names, in the same pass, even though (the
|
|
281
|
+
attribute section below explains why) no gate will ever check that you
|
|
282
|
+
did:
|
|
283
|
+
|
|
284
|
+
```markdown
|
|
285
|
+
---
|
|
286
|
+
id: q-account-lockout-scope
|
|
287
|
+
severity: moderate
|
|
288
|
+
status: open
|
|
289
|
+
---
|
|
290
|
+
|
|
291
|
+
## Evidence
|
|
292
|
+
|
|
293
|
+
`Controllers/AuthController.cs:8` carries a `TODO: lock the account after 5
|
|
294
|
+
failed attempts; not implemented yet` comment directly above `Login`. The
|
|
295
|
+
rule-sweep found this as a probe hit; it is not implemented, so it cannot
|
|
296
|
+
become a requirement describing current behavior, and it duplicates the
|
|
297
|
+
same open question `q-users-islocked-semantics` (below) raises about the
|
|
298
|
+
`IsLocked` column.
|
|
299
|
+
|
|
300
|
+
## Options
|
|
301
|
+
|
|
302
|
+
(a) Write it as a requirement for the target anyway, since it is documented
|
|
303
|
+
intent. (b) Leave it out of scope entirely; the legacy system never enforced
|
|
304
|
+
it. (c) Fold it into `q-users-islocked-semantics` and let the operator rule
|
|
305
|
+
on both at once.
|
|
306
|
+
|
|
307
|
+
## Recommendation
|
|
308
|
+
|
|
309
|
+
Recommend (c); both items are one open question (does lockout exist or
|
|
310
|
+
not), not two.
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
`migrate queue add q-account-lockout-scope.md` accepts this and prints
|
|
314
|
+
`queue add: q-account-lockout-scope [moderate]`.
|
|
315
|
+
|
|
316
|
+
**A rule-sweep with nothing to report still needs a record**, the same
|
|
317
|
+
zero-findings discipline enumerate.md states for a lens: `probes: N,
|
|
318
|
+
found: 0, as_requirements: 0, queued: []` is a real, closeable record, not
|
|
319
|
+
a reason to skip writing one.
|
|
320
|
+
|
|
321
|
+
### The attribute lens
|
|
322
|
+
|
|
323
|
+
**The lens contract from enumerate applies one level down, to any surface
|
|
324
|
+
with sub-elements: table to columns, report to parameters, screen to
|
|
325
|
+
fields, endpoint to parameters.** An `attribute` census needs at least two
|
|
326
|
+
independent directions, each naming its evidence, exactly like a `lens`
|
|
327
|
+
census, and `total` is bounded the same way:
|
|
328
|
+
`max(directions) <= total <= sum(directions)`. Balance:
|
|
329
|
+
`explained + queued.length == behavioral`.
|
|
330
|
+
|
|
331
|
+
**The exemption list is judgment this manual holds, not code: identity
|
|
332
|
+
keys, audit stamps, and tenant discriminators are exempt from
|
|
333
|
+
`behavioral`.** A column that only exists to be a primary key, or to record
|
|
334
|
+
who created or last touched a row and when, or to say which tenant a row
|
|
335
|
+
belongs to, carries no behavior of its own to explain; counting it toward
|
|
336
|
+
`behavioral` would demand an "explanation" for something that has none.
|
|
337
|
+
Nothing enforces this list in code, the same way enumerate.md's naming
|
|
338
|
+
convention for skipped elements is a convention rather than a check: two
|
|
339
|
+
different agents can disagree about whether a given column is exempt, and
|
|
340
|
+
the tool has no opinion either way.
|
|
341
|
+
|
|
342
|
+
A worked example, run against a real store. The `Users` table has five
|
|
343
|
+
columns: `Id` (identity key, exempt), `Email`, `PasswordHash`, `CreatedAt`
|
|
344
|
+
(audit stamp, exempt), and `IsLocked`. The `ddl` direction counted all five
|
|
345
|
+
from the `CREATE TABLE` statement; `entity` counted four properties on the
|
|
346
|
+
EF entity class (one column has no mapped property). `Email` and
|
|
347
|
+
`PasswordHash` are both explained by UM-001 directly; `IsLocked` is neither
|
|
348
|
+
exempt nor explained by anything on record, and the account-lockout comment
|
|
349
|
+
that `q-account-lockout-scope` already raised makes its real semantics an
|
|
350
|
+
open question, not a settled one, so it is queued rather than guessed at.
|
|
351
|
+
|
|
352
|
+
```json
|
|
353
|
+
{
|
|
354
|
+
"kind": "attribute",
|
|
355
|
+
"surface": "tables",
|
|
356
|
+
"subject": "table-users",
|
|
357
|
+
"phase": "extract",
|
|
358
|
+
"directions": {
|
|
359
|
+
"ddl": { "count": 5, "evidence": "column list from CREATE TABLE Users" },
|
|
360
|
+
"entity": { "count": 4, "evidence": "properties on the EF User entity class" }
|
|
361
|
+
},
|
|
362
|
+
"total": 5,
|
|
363
|
+
"behavioral": 3,
|
|
364
|
+
"explained": 2,
|
|
365
|
+
"queued": ["q-users-islocked-semantics"],
|
|
366
|
+
"batch": "b-attr-users-001"
|
|
367
|
+
}
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
`migrate census attr-table-users.json` accepts this and prints `census:
|
|
371
|
+
recorded attribute:table-users`. The same shape failures the lens census
|
|
372
|
+
enforces apply here too, run against a real store: the old bare-count
|
|
373
|
+
shape (`"directions": {"ddl": 8, "entity": 7}`) is rejected by name on both
|
|
374
|
+
fields at once (`directions.ddl uses the old bare-count shape...`), a
|
|
375
|
+
single direction is rejected (`directions needs at least two independent
|
|
376
|
+
directions; the lens contract does not admit a single-direction
|
|
377
|
+
enumeration`), and an imbalanced record is rejected naming the exact
|
|
378
|
+
mismatch (`attribute census for table-users does not balance: behavioral 3
|
|
379
|
+
but explained 1 + queued 1 = 2`).
|
|
380
|
+
|
|
381
|
+
File the item this record names, the same as rule-sweep's above:
|
|
382
|
+
|
|
383
|
+
```markdown
|
|
384
|
+
---
|
|
385
|
+
id: q-users-islocked-semantics
|
|
386
|
+
severity: moderate
|
|
387
|
+
status: open
|
|
388
|
+
---
|
|
389
|
+
|
|
390
|
+
## Evidence
|
|
391
|
+
|
|
392
|
+
The `Users` table's `IsLocked` column is not an identity key, an audit
|
|
393
|
+
stamp, or a tenant discriminator, so it is not on the attribute lens's
|
|
394
|
+
exemption list, and it is not written or read anywhere in
|
|
395
|
+
`AuthController.cs`. The `Login` method's own comment says account lockout
|
|
396
|
+
"is not implemented yet."
|
|
397
|
+
|
|
398
|
+
## Options
|
|
399
|
+
|
|
400
|
+
(a) Treat it as dead state and drop it from the target schema. (b) Treat it
|
|
401
|
+
as a real but unenforced rule and implement lockout in the target. (c) Ask
|
|
402
|
+
the operator which one matches actual production behavior.
|
|
403
|
+
|
|
404
|
+
## Recommendation
|
|
405
|
+
|
|
406
|
+
Recommend (c); the column and the comment disagree about whether lockout
|
|
407
|
+
exists, and only the operator can say which one is true today.
|
|
408
|
+
```
|
|
409
|
+
|
|
410
|
+
`migrate queue add q-users-islocked-semantics.md` accepts this and prints
|
|
411
|
+
`queue add: q-users-islocked-semantics [moderate]`.
|
|
412
|
+
|
|
413
|
+
**The honest limit: nothing checks that every attribute-bearing element
|
|
414
|
+
actually got an attribute census, or that every capability actually got a
|
|
415
|
+
rule-sweep.** Verified on a disposable copy of the store: stripping both
|
|
416
|
+
kinds of record out of `census.jsonl` there still passes `migrate check`
|
|
417
|
+
clean, because gate 2 only
|
|
418
|
+
tracks completeness for `lens` (against `[surfaces].types`) and `closer`
|
|
419
|
+
(against `[closers].set`); `attribute` and `rule-sweep` records are
|
|
420
|
+
balance-checked when present but never counted against any declared list.
|
|
421
|
+
Doing the sweep for every capability and the attribute pass for every
|
|
422
|
+
table, report, and screen with sub-elements is this manual's discipline,
|
|
423
|
+
not the tool's gate, the same way the closer set below is enforced by name
|
|
424
|
+
and these two kinds are not.
|
|
425
|
+
|
|
426
|
+
### The declared closer set
|
|
427
|
+
|
|
428
|
+
**Every closer in `[closers].set` needs exactly one `closer` census
|
|
429
|
+
record; a declared closer with no record fails the gate by name.** Balance:
|
|
430
|
+
`findings == fixed + queued.length`. `checked` is informational and may be
|
|
431
|
+
zero: a closer that genuinely finds nothing to check in a small or
|
|
432
|
+
single-capability run still closes with a real record, not a skip.
|
|
433
|
+
|
|
434
|
+
The default three, and what each looks for:
|
|
435
|
+
|
|
436
|
+
- **cross-capability-workflow.** A user journey that spans more than one
|
|
437
|
+
capability (checkout touching a cart capability, a payment capability,
|
|
438
|
+
and an inventory capability) can get lost when capabilities are mined
|
|
439
|
+
independently, each agent seeing only its own slice. This closer checks
|
|
440
|
+
for exactly that seam.
|
|
441
|
+
- **scope-injection.** An FR that claims more than its citations actually
|
|
442
|
+
support: configurable multi-factor authentication written up as a
|
|
443
|
+
requirement when the source only ever shows a password check. This
|
|
444
|
+
closer checks every FR against what it cites, not against what sounds
|
|
445
|
+
plausible.
|
|
446
|
+
- **read-write-symmetry.** Every write path should have a matching read or
|
|
447
|
+
verification path, and vice versa. A write with no matching read is
|
|
448
|
+
worth naming even when it might turn out to be a false alarm (the
|
|
449
|
+
verification endpoint exists somewhere this pass did not look), because
|
|
450
|
+
the alternative is a defect nobody ever went looking for.
|
|
451
|
+
|
|
452
|
+
Three worked examples, run against the same real store, one per closer,
|
|
453
|
+
each closing on the shape its own finding actually took: no finding at
|
|
454
|
+
all, a finding fixed on the spot, and a finding that needed the queue.
|
|
455
|
+
|
|
456
|
+
**cross-capability-workflow finds nothing here, and that is a real,
|
|
457
|
+
closeable answer, not a skip.** This run has exactly one capability
|
|
458
|
+
(`user-management`), so no workflow inside it can possibly cross into a
|
|
459
|
+
second one; a closer whose whole job is catching a cross-capability seam
|
|
460
|
+
correctly reports zero findings on a single-capability run, checked or not.
|
|
461
|
+
|
|
462
|
+
```json
|
|
463
|
+
{
|
|
464
|
+
"kind": "closer",
|
|
465
|
+
"closer": "cross-capability-workflow",
|
|
466
|
+
"phase": "extract",
|
|
467
|
+
"checked": 2,
|
|
468
|
+
"findings": 0,
|
|
469
|
+
"fixed": 0,
|
|
470
|
+
"queued": [],
|
|
471
|
+
"batch": "b-closer-cross-capability-001"
|
|
472
|
+
}
|
|
473
|
+
```
|
|
474
|
+
|
|
475
|
+
`migrate census closer-cross-capability.json` accepts this and prints
|
|
476
|
+
`census: recorded closer:cross-capability-workflow`.
|
|
477
|
+
|
|
478
|
+
**scope-injection catches a finding and fixes it on the spot, no queue
|
|
479
|
+
needed.** A draft of UM-003 originally asserted that the reset token
|
|
480
|
+
expires after 15 minutes; nothing in `AuthController.cs` shows any
|
|
481
|
+
expiry logic at all, so the closer flagged the claim and the sentence was
|
|
482
|
+
trimmed out of the requirement before it was ever imported. `fixed` records
|
|
483
|
+
exactly this: a finding this pass could resolve itself, without an owner.
|
|
484
|
+
|
|
485
|
+
```json
|
|
486
|
+
{
|
|
487
|
+
"kind": "closer",
|
|
488
|
+
"closer": "scope-injection",
|
|
489
|
+
"phase": "extract",
|
|
490
|
+
"checked": 3,
|
|
491
|
+
"findings": 1,
|
|
492
|
+
"fixed": 1,
|
|
493
|
+
"queued": [],
|
|
494
|
+
"batch": "b-closer-scope-injection-001"
|
|
495
|
+
}
|
|
496
|
+
```
|
|
497
|
+
|
|
498
|
+
`migrate census closer-scope-injection.json` accepts this and prints
|
|
499
|
+
`census: recorded closer:scope-injection`.
|
|
500
|
+
|
|
501
|
+
**read-write-symmetry finds something it cannot resolve itself, so it goes
|
|
502
|
+
to the queue.** `ResetPassword` writes a reset token and emails it, but no
|
|
503
|
+
controller in the checkout reads or verifies a submitted token.
|
|
504
|
+
|
|
505
|
+
```json
|
|
506
|
+
{
|
|
507
|
+
"kind": "closer",
|
|
508
|
+
"closer": "read-write-symmetry",
|
|
509
|
+
"phase": "extract",
|
|
510
|
+
"checked": 3,
|
|
511
|
+
"findings": 1,
|
|
512
|
+
"fixed": 0,
|
|
513
|
+
"queued": ["q-reset-token-verify-missing"],
|
|
514
|
+
"batch": "b-closer-read-write-symmetry-001"
|
|
515
|
+
}
|
|
516
|
+
```
|
|
517
|
+
|
|
518
|
+
`migrate census closer-read-write-symmetry.json` accepts this and prints
|
|
519
|
+
`census: recorded closer:read-write-symmetry`. A closer record must
|
|
520
|
+
declare `"phase": "extract"`; declaring anything else is rejected at write
|
|
521
|
+
time, by name, the mirror image of the lens/`enumerate` constraint
|
|
522
|
+
enumerate.md documents: `closer census must declare phase "extract", the
|
|
523
|
+
only phase gate 10 (run-state) checks its batch against; found
|
|
524
|
+
"enumerate"`.
|
|
525
|
+
|
|
526
|
+
File the item this closer named:
|
|
527
|
+
|
|
528
|
+
```markdown
|
|
529
|
+
---
|
|
530
|
+
id: q-reset-token-verify-missing
|
|
531
|
+
severity: critical
|
|
532
|
+
status: open
|
|
533
|
+
---
|
|
534
|
+
|
|
535
|
+
## Evidence
|
|
536
|
+
|
|
537
|
+
`read-write-symmetry` checked every write path against a matching read
|
|
538
|
+
path. `ResetPassword` writes a reset token via `GenerateResetToken` and
|
|
539
|
+
emails it, but no controller in the source reads or verifies a submitted
|
|
540
|
+
token: there is no `POST /api/password-reset/confirm` or equivalent. Either
|
|
541
|
+
the verification endpoint exists somewhere this pass did not look, or reset
|
|
542
|
+
tokens are issued and never checked.
|
|
543
|
+
|
|
544
|
+
## Options
|
|
545
|
+
|
|
546
|
+
(a) Widen the search (other controllers, an area folder, a separate
|
|
547
|
+
service) before concluding it is missing. (b) Treat it as a real gap and
|
|
548
|
+
flag it for the target to fix, not replicate. (c) Ask the operator directly
|
|
549
|
+
whether reset ever worked end-to-end in production.
|
|
550
|
+
|
|
551
|
+
## Recommendation
|
|
552
|
+
|
|
553
|
+
Recommend (c); a write with no matching read is exactly what this closer
|
|
554
|
+
exists to catch, and only the operator can say whether it is a real defect
|
|
555
|
+
or evidence this pass has not looked far enough yet.
|
|
556
|
+
```
|
|
557
|
+
|
|
558
|
+
`migrate queue add q-reset-token-verify-missing.md` accepts this and
|
|
559
|
+
prints `queue add: q-reset-token-verify-missing [critical]` (queue.md's
|
|
560
|
+
own worked grammar example is built on this exact file).
|
|
561
|
+
|
|
562
|
+
A declared closer with no record at all fails `census`, run on a
|
|
563
|
+
disposable copy with extract reset before any closer census was recorded
|
|
564
|
+
(never reset the running example itself just to see this message):
|
|
565
|
+
|
|
566
|
+
```
|
|
567
|
+
census:
|
|
568
|
+
declared closer cross-capability-workflow has no census record
|
|
569
|
+
declared closer scope-injection has no census record
|
|
570
|
+
declared closer read-write-symmetry has no census record
|
|
571
|
+
```
|
|
572
|
+
|
|
573
|
+
### Workflow tracing
|
|
574
|
+
|
|
575
|
+
A requirement should describe a whole user journey, not stop at the first
|
|
576
|
+
method that handles part of it. `Login` validates credentials and issues a
|
|
577
|
+
token; a real workflow trace follows that token forward to whatever
|
|
578
|
+
subsequently checks it, not just the one method that mints it. On a larger
|
|
579
|
+
source, one journey routinely crosses more than one controller, or a
|
|
580
|
+
controller and a background job together; cross-capability-workflow above
|
|
581
|
+
is exactly the closer that catches a journey mined this way and split
|
|
582
|
+
across two agents' capabilities without anyone noticing the seam.
|
|
583
|
+
|
|
584
|
+
## What closes it
|
|
585
|
+
|
|
586
|
+
Every capability mined, every element disposed, every declared closer
|
|
587
|
+
recorded, every queue item this phase's own findings named already filed
|
|
588
|
+
(above). `migrate check --phase extract` will not read as clean mid-run,
|
|
589
|
+
which is expected: the census gate reads the whole store regardless of
|
|
590
|
+
`--phase`, so it still names every surface this scratch run never
|
|
591
|
+
enumerated (`jobs`, `reports`, `screens`, `integrations`, `workflows`,
|
|
592
|
+
`settings`). `refs` does not appear below only because `q-legacy-admin-tool`
|
|
593
|
+
was already filed above; skip that step and it reappears here, naming
|
|
594
|
+
`route-get-legacy-admin-tool`, exactly the way enumerate.md's and seam.md's
|
|
595
|
+
own noisy-but-expected checks name what is genuinely still missing rather
|
|
596
|
+
than padding the count. Run `migrate check --phase extract` for real,
|
|
597
|
+
right before flipping the phase, against a store with exactly the rows
|
|
598
|
+
this manual's examples built:
|
|
599
|
+
|
|
600
|
+
```
|
|
601
|
+
4/5 mapped, 1 out-of-scope, 0 unaccounted
|
|
602
|
+
|
|
603
|
+
Violations (10):
|
|
604
|
+
census:
|
|
605
|
+
declared surface jobs has no lens census record; the lens did not run or did not close
|
|
606
|
+
declared surface reports has no lens census record; the lens did not run or did not close
|
|
607
|
+
declared surface screens has no lens census record; the lens did not run or did not close
|
|
608
|
+
declared surface integrations has no lens census record; the lens did not run or did not close
|
|
609
|
+
declared surface workflows has no lens census record; the lens did not run or did not close
|
|
610
|
+
declared surface settings has no lens census record; the lens did not run or did not close
|
|
611
|
+
parity:
|
|
612
|
+
UM-001 has no parity plan
|
|
613
|
+
UM-002 has no parity plan
|
|
614
|
+
UM-003 has no parity plan
|
|
615
|
+
run-state:
|
|
616
|
+
phase extract is running; every phase through extract must be done
|
|
617
|
+
```
|
|
618
|
+
|
|
619
|
+
The `parity` lines are expected too, for the same reason enumerate.md and
|
|
620
|
+
seam.md give for their own noisy mid-run checks: assigning an oracle is
|
|
621
|
+
phase 4's job, and the `run-state` line clears the moment the phase is
|
|
622
|
+
flipped, not before. Once every capability is mined and every declared
|
|
623
|
+
closer has closed, flip it:
|
|
624
|
+
|
|
625
|
+
```
|
|
626
|
+
migrate phase extract --status done
|
|
627
|
+
```
|
|
628
|
+
|
|
629
|
+
## Degradation
|
|
630
|
+
|
|
631
|
+
- **No recipe for the detected stack.** Contract-only, exactly as in
|
|
632
|
+
enumerate: derive your own attribute directions and rule-sweep probes;
|
|
633
|
+
the census still gates identically.
|
|
634
|
+
- **No runtime environment.** The evidence trust order loses its top tier;
|
|
635
|
+
confidence rests on `code`, `nav`, and `docs` alone, and a behavior only
|
|
636
|
+
a live run could confirm is `inferred` at best, never `confirmed`.
|
|
637
|
+
- **A rule-sweep or attribute census with nothing to report.** Record that
|
|
638
|
+
fact explicitly, the same zero-findings discipline as a lens: a real
|
|
639
|
+
record with zero counts, not a skipped one.
|
|
640
|
+
- **A declared closer that finds nothing to check.** `checked: 0, findings:
|
|
641
|
+
0` is a valid, closeable record for a closer that genuinely does not
|
|
642
|
+
apply at this run's current scale, not a reason to omit it.
|
|
643
|
+
|
|
644
|
+
## Commands
|
|
645
|
+
|
|
646
|
+
```
|
|
647
|
+
migrate import reqs <batch.json>
|
|
648
|
+
migrate import elements <batch.json>
|
|
649
|
+
migrate census <record.json>
|
|
650
|
+
migrate queue add <item.md>
|
|
651
|
+
migrate phase extract --status done
|
|
652
|
+
```
|