@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,416 @@
|
|
|
1
|
+
# Phase 2: Seam
|
|
2
|
+
|
|
3
|
+
## Purpose
|
|
4
|
+
|
|
5
|
+
Partition the ledger into capabilities: the seam the target will be built
|
|
6
|
+
along. Two-of-three triangulation across up to four validators, escalating
|
|
7
|
+
to the queue on disagreement or low modularity rather than choosing
|
|
8
|
+
silently. Exit condition: `capabilities.jsonl`, `seam.json`, and `seam.md`
|
|
9
|
+
exist and agree with each other, and `migrate phase seam --status done`
|
|
10
|
+
has run.
|
|
11
|
+
|
|
12
|
+
## Inputs
|
|
13
|
+
|
|
14
|
+
- `config.toml`: `source.vcs` (whether change-coupling analysis can run at
|
|
15
|
+
all), `source.path` (where the schema and code live for the other two
|
|
16
|
+
validators).
|
|
17
|
+
- The store: `elements.jsonl`, specifically the `refs` already recorded on
|
|
18
|
+
every element from enumerate. `requirements.jsonl` does not exist yet at
|
|
19
|
+
this point in the run (extract is the next phase, not this one), so
|
|
20
|
+
`refs` is the only Ref-shaped field seam can read; surface-affinity
|
|
21
|
+
clustering builds its graph from exactly this, and nothing else.
|
|
22
|
+
|
|
23
|
+
## Procedure
|
|
24
|
+
|
|
25
|
+
Run whichever of these are available. None of the four is mandatory on its
|
|
26
|
+
own; two of whichever ran must agree, at modularity Q >= 0.3, before you
|
|
27
|
+
accept a partition (the one-validator exception is below).
|
|
28
|
+
|
|
29
|
+
1. **Schema clustering.** Cluster tables by foreign-key connectivity, where
|
|
30
|
+
a relational schema exists to read.
|
|
31
|
+
2. **Call-graph community detection.** Cluster by static call structure,
|
|
32
|
+
where the code is statically parseable.
|
|
33
|
+
3. **Change-coupling analysis.** Cluster by co-change frequency in VCS
|
|
34
|
+
history, where VCS history exists.
|
|
35
|
+
4. **Surface-affinity clustering.** Build a graph straight off the `refs`
|
|
36
|
+
already sitting in `elements.jsonl`, and run community detection over
|
|
37
|
+
it. It needs no parseable code, no relational schema, and no VCS
|
|
38
|
+
history: only the ledger, which by construction always exists once
|
|
39
|
+
enumerate has run. That makes it the fallback validator when the other
|
|
40
|
+
three cannot run at all, and a fourth opinion checking the other three
|
|
41
|
+
when they can. The acceptance rule follows immediately below; the
|
|
42
|
+
concrete method and worked examples come after it.
|
|
43
|
+
|
|
44
|
+
**Triangulate.** Accept a partition when two of the validators that ran
|
|
45
|
+
agree with each other, at modularity Q >= 0.3 on the agreed partition.
|
|
46
|
+
Disagreement (no two agree) or low Q on the best candidate both escalate to
|
|
47
|
+
the queue: name the evidence, the disagreeing outputs or the Q figure, list
|
|
48
|
+
the real options, and recommend one. **Vertical-slice-only** (one
|
|
49
|
+
capability per user journey, no cross-cutting split) is the ratified
|
|
50
|
+
fallback option in that queue item. It is offered for an owner to choose,
|
|
51
|
+
never picked silently in its place.
|
|
52
|
+
|
|
53
|
+
When fewer than two validators can run at all (the fully degraded case:
|
|
54
|
+
no schema, no parseable call graph, and no VCS, so only surface-affinity
|
|
55
|
+
is left), there is no pair to agree with. That lone validator's own Q
|
|
56
|
+
against the 0.3 floor is what "validator of last resort" means in
|
|
57
|
+
practice: Q >= 0.3 on its own is accepted with no second opinion behind
|
|
58
|
+
it, and Q below 0.3 escalates the same as any other low-Q result. Q here
|
|
59
|
+
means whatever the clustering step actually produced, the refinement below
|
|
60
|
+
included when a component needed it, never the components-only figure by
|
|
61
|
+
default; a lone validator's number that skipped a refinement it needed is
|
|
62
|
+
not this case, it is just an unfinished run. This is the only path that
|
|
63
|
+
lets one validator's own number close the phase by itself; every other
|
|
64
|
+
case still needs the two-agree rule above, including every case where
|
|
65
|
+
surface-affinity is merely one of several validators that ran rather than
|
|
66
|
+
the only one.
|
|
67
|
+
|
|
68
|
+
### Surface-affinity clustering, worked
|
|
69
|
+
|
|
70
|
+
**Build the graph.** One node per element id. One edge for every
|
|
71
|
+
`{"kind": "ledger", "id": "..."}` entry in any element's `refs`, connecting
|
|
72
|
+
that element to the id it names. This is not a bipartite graph of "surface
|
|
73
|
+
types" against "table types" specifically: it is one graph over every
|
|
74
|
+
element id that carries a `ledger` ref, whatever surface each end happens
|
|
75
|
+
to be, since a job or a screen can touch a table exactly the same way a
|
|
76
|
+
route does. No library is required to build it; it is a plain adjacency
|
|
77
|
+
map from parsing `elements.jsonl`.
|
|
78
|
+
|
|
79
|
+
These refs are not something this phase produces itself: they come from
|
|
80
|
+
`references/phases/enumerate.md`'s Procedure, step 4, which is where a
|
|
81
|
+
lens records that an element it just found touches one already in the
|
|
82
|
+
ledger. If that step was skipped for real touches that exist in the
|
|
83
|
+
source, this graph has no edge for them, silently; there is no check here
|
|
84
|
+
or anywhere else that notices a missing ref, only one (in `enumerate.md`'s
|
|
85
|
+
own step 4) that notices a `ledger` ref pointing at an id the ledger does
|
|
86
|
+
not yet have.
|
|
87
|
+
|
|
88
|
+
**Cluster it.** Connected components: two elements with no path of `refs`
|
|
89
|
+
between them cannot possibly share a capability, so start by splitting the
|
|
90
|
+
graph into its connected pieces. This needs nothing beyond a breadth-first
|
|
91
|
+
search, no installed graph library, which matters most here because this
|
|
92
|
+
is the validator you reach for when nothing else about the source can be
|
|
93
|
+
assumed either. Confirm the split with modularity, using the textbook
|
|
94
|
+
formula directly rather than a library call:
|
|
95
|
+
|
|
96
|
+
```
|
|
97
|
+
Q = sum over communities c of [ (edges_within_c / m) - (degree_sum_c / (2m))^2 ]
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
where `m` is the total edge count and `degree_sum_c` is the sum of every
|
|
101
|
+
node's degree inside community `c`. `edges_within_c` is just every edge
|
|
102
|
+
whose both ends fall inside `c`; when the communities are exactly the
|
|
103
|
+
connected components, that is every edge each node has, since a connected
|
|
104
|
+
component has no edge leaving it by definition. If one connected component
|
|
105
|
+
is still large and its internal structure is not obviously one capability,
|
|
106
|
+
the same formula supports going further with a standard greedy modularity
|
|
107
|
+
merge (repeatedly join whichever two sub-groups raise Q the most, stop when
|
|
108
|
+
no join helps). A sub-group here is a single node inside the component
|
|
109
|
+
being refined, not a whole component: every node in that component starts
|
|
110
|
+
as its own group, and the merges run only among those. Merging whole
|
|
111
|
+
components together, by contrast, can never raise Q, since components
|
|
112
|
+
share no edges: the within-community edge count stays exactly what it
|
|
113
|
+
was while the squared-degree term only grows. A reader who tries that and
|
|
114
|
+
watches Q fall on the first join has learned only that components were the
|
|
115
|
+
wrong thing to merge, not that the partition is already the best
|
|
116
|
+
available. The first worked example below clears the 0.3 floor on
|
|
117
|
+
connected components alone and does not need this refinement; the second,
|
|
118
|
+
right after it, does, and runs it to completion.
|
|
119
|
+
|
|
120
|
+
**Project onto capabilities.** Every node in one community, regardless of
|
|
121
|
+
which surface it came from, becomes that capability's `elements` array,
|
|
122
|
+
verbatim. No separate step drops the table nodes: a capability's element
|
|
123
|
+
list mixing a route id and a table id (as in the `capabilities.jsonl`
|
|
124
|
+
example already shown) is exactly what a community looks like once you
|
|
125
|
+
stop distinguishing surfaces.
|
|
126
|
+
|
|
127
|
+
**Edgeless elements, and where they land.** An element carrying no `ledger`
|
|
128
|
+
ref, and named by none, is its own connected component: a singleton with no
|
|
129
|
+
edges at all. Expect a real share of them, not a handful; both committed
|
|
130
|
+
fixtures are about a quarter edgeless (three of `tiny-express`'s twelve,
|
|
131
|
+
four of `tiny-webforms`'s sixteen). Two facts about them, in the order they
|
|
132
|
+
matter:
|
|
133
|
+
|
|
134
|
+
1. **They do not move Q, in either direction.** A singleton community has
|
|
135
|
+
no within-community edge and zero total degree, so its term in the
|
|
136
|
+
formula above is `0 - 0`. Leave them out of the component listing and
|
|
137
|
+
the arithmetic; including them changes nothing but the line count. This
|
|
138
|
+
is why a fixture can be a quarter edgeless and still clear the floor.
|
|
139
|
+
2. **They must still land in exactly one capability, and nothing computes
|
|
140
|
+
which.** The graph holds no information about a node with no edges,
|
|
141
|
+
which is what being edgeless means, so this is a judgment call made once
|
|
142
|
+
by hand and written down. Assign each edgeless element to the capability
|
|
143
|
+
already holding what it sits closest to in the source: the same file,
|
|
144
|
+
the same directory, the same feature area, the same configuration block.
|
|
145
|
+
Name the capability and the basis in `seam.md`, one line per edgeless
|
|
146
|
+
element, beside the validators' output.
|
|
147
|
+
|
|
148
|
+
Two things this rule is not.
|
|
149
|
+
|
|
150
|
+
It is not **one capability per edgeless element**. A capability per orphan
|
|
151
|
+
is not a partition of anything; it defeats the fanout the seam exists to
|
|
152
|
+
produce, since extract dispatches one agent per capability and would then
|
|
153
|
+
dispatch one per stray settings key.
|
|
154
|
+
|
|
155
|
+
It is not **leave it out of every capability**. Nothing here catches that:
|
|
156
|
+
no gate reads `capabilities.jsonl`'s `elements` array at all, at any phase.
|
|
157
|
+
`check.ts` reads that file only for the `slug` set, to reject duplicate
|
|
158
|
+
slugs and to resolve each requirement's `cap`. So an element in no
|
|
159
|
+
capability passes every gate this phase and the next one have, and then
|
|
160
|
+
surfaces two phases later in the most expensive possible way: extract fans
|
|
161
|
+
out per capability, an element in no capability gets no agent, no agent
|
|
162
|
+
writes it a disposition, and the coverage gate fails on an `unaccounted`
|
|
163
|
+
element with nothing in the message pointing back at the seam decision that
|
|
164
|
+
caused it.
|
|
165
|
+
|
|
166
|
+
The one exception is deliberate rather than accidental. When no capability
|
|
167
|
+
is a defensible home (a connection string every capability's data path
|
|
168
|
+
reads, a setting genuinely shared by all of them), that is a seam finding,
|
|
169
|
+
not an element problem: file a queue item naming the element, the
|
|
170
|
+
capabilities with a partial claim, and the real options, and leave it out
|
|
171
|
+
of every capability on purpose. Extract then disposes of it `out-of-scope`
|
|
172
|
+
citing that same item, which is the one route by which an element reaches a
|
|
173
|
+
terminal disposition without belonging to a capability, and the `refs` gate
|
|
174
|
+
does check that queue id resolves. Deliberate and recorded; the failure
|
|
175
|
+
above is silent and unrecorded, and that is the whole difference between
|
|
176
|
+
them.
|
|
177
|
+
|
|
178
|
+
A worked example, run against a real store, of the fully degraded case
|
|
179
|
+
above: assume this source has no relational schema, no statically
|
|
180
|
+
parseable code, and no VCS history at all, so surface-affinity is the
|
|
181
|
+
only validator that can run, and the one-validator exception is what will
|
|
182
|
+
license accepting its result. Six elements, imported as one batch: two
|
|
183
|
+
routes and a table that only they touch, and a route and a job that only a
|
|
184
|
+
different table touches.
|
|
185
|
+
|
|
186
|
+
```json
|
|
187
|
+
{"id": "route-get-api-users", "surface": "routes", "refs": [{"kind": "ledger", "id": "table-users"}], "...": "..."}
|
|
188
|
+
{"id": "route-post-api-login", "surface": "routes", "refs": [{"kind": "ledger", "id": "table-users"}], "...": "..."}
|
|
189
|
+
{"id": "table-users", "surface": "tables", "refs": [], "...": "..."}
|
|
190
|
+
{"id": "route-get-api-invoices", "surface": "routes", "refs": [{"kind": "ledger", "id": "table-invoices"}], "...": "..."}
|
|
191
|
+
{"id": "job-nightly-invoice-export", "surface": "jobs", "refs": [{"kind": "ledger", "id": "table-invoices"}], "...": "..."}
|
|
192
|
+
{"id": "table-invoices", "surface": "tables", "refs": [], "...": "..."}
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
`migrate import elements` accepts all six (`import elements: 6 added, 0
|
|
196
|
+
updated, batch b-seam-example-001`; the `, batch <id>` suffix is
|
|
197
|
+
unconditional, since the importer writes the batch id onto every row). A
|
|
198
|
+
short standard-library script (no `pip install`, no graph
|
|
199
|
+
package) reads the four `ledger` refs as edges, finds connected
|
|
200
|
+
components by breadth-first search, and computes Q with the formula
|
|
201
|
+
above. Its real output, unedited:
|
|
202
|
+
|
|
203
|
+
```
|
|
204
|
+
community 1: ['route-get-api-users', 'route-post-api-login', 'table-users']
|
|
205
|
+
community 2: ['job-nightly-invoice-export', 'route-get-api-invoices', 'table-invoices']
|
|
206
|
+
m (total edges) = 4
|
|
207
|
+
modularity Q = 0.5
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
`0.5 >= 0.3`, and since no schema, call graph, or VCS history exists for
|
|
211
|
+
this source, surface-affinity is the only validator that ran: the
|
|
212
|
+
one-validator exception above, not the ordinary two-agree rule, is what
|
|
213
|
+
licenses accepting this split on that number alone. Each community becomes
|
|
214
|
+
one capability, its node set copied straight into `elements`:
|
|
215
|
+
|
|
216
|
+
```json
|
|
217
|
+
{"slug": "user-management", "title": "User Management", "ns": "UM", "elements": ["route-get-api-users", "route-post-api-login", "table-users"]}
|
|
218
|
+
{"slug": "invoicing", "title": "Invoicing", "ns": "INV", "elements": ["job-nightly-invoice-export", "route-get-api-invoices", "table-invoices"]}
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
Both lines, appended to `.migrate/capabilities.jsonl`, are accepted as-is.
|
|
222
|
+
Had this source had a schema or a parseable call graph to try as well, or
|
|
223
|
+
had any second validator disagreed with this one, the ordinary two-agree
|
|
224
|
+
rule above would govern instead, and this Q alone would not have been
|
|
225
|
+
enough.
|
|
226
|
+
|
|
227
|
+
A second worked example, where connected components alone do not clear the
|
|
228
|
+
floor and the refinement above does: `fixtures/tiny-webforms`, built from
|
|
229
|
+
the refs `GROUND-TRUTH.md`'s "Element-to-element touches" section documents
|
|
230
|
+
and `scripts/__tests__/e2e-webforms.test.ts` records by hand, the same way a
|
|
231
|
+
lens would. Twelve of this fixture's sixteen elements carry or receive a
|
|
232
|
+
ref; the other four have none and drop out as edgeless singleton
|
|
233
|
+
components, which contribute nothing to Q, so they are left out of the
|
|
234
|
+
listing below. `m = 10` edges. A standard-library script builds the graph
|
|
235
|
+
from those refs, finds components by breadth-first search, and computes Q
|
|
236
|
+
with the formula above. Its real output, unedited:
|
|
237
|
+
|
|
238
|
+
```
|
|
239
|
+
component 1 (n=10): integration-billing-sync, report-daily-users,
|
|
240
|
+
route-get-api-users, route-get-api-users-id-welcome, route-post-api-users,
|
|
241
|
+
screen-default, screen-users, setting-welcome-email-enabled, table-users,
|
|
242
|
+
workflow-signup-welcome
|
|
243
|
+
component 2 (n=2): job-nightly-digest, table-audit-log
|
|
244
|
+
m (total edges) = 10
|
|
245
|
+
components-only Q = 0.180
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
`0.18 < 0.3`: on connected components alone, this seam would escalate. The
|
|
249
|
+
ten-node component is the one that is "still large and its internal
|
|
250
|
+
structure is not obviously one capability" (seven different surfaces mixed
|
|
251
|
+
together, every declared surface but jobs, with no visible split); the
|
|
252
|
+
two-node component is left alone, since a job and the one table it purges
|
|
253
|
+
is already obviously one capability. Every node in the ten-node component
|
|
254
|
+
starts as its own group, per the clause above, while the two-node component
|
|
255
|
+
stays whole; exploded to eleven groups like that, the partition starts at
|
|
256
|
+
`Q = -0.015`, below even the components-only figure, since none of the ten
|
|
257
|
+
singletons has counted a same-group edge yet. The standard greedy merge
|
|
258
|
+
runs from there. Its real output, unedited:
|
|
259
|
+
|
|
260
|
+
```
|
|
261
|
+
starting Q (ten-node component exploded to singletons) = -0.015
|
|
262
|
+
merge 1: {report-daily-users} + {table-users} -> Q = 0.070
|
|
263
|
+
merge 2: {route-post-api-users} + {screen-default} -> Q = 0.155
|
|
264
|
+
merge 3: {setting-welcome-email-enabled} + {workflow-signup-welcome} -> Q = 0.240
|
|
265
|
+
merge 4: {integration-billing-sync} + {route-get-api-users-id-welcome} -> Q = 0.325
|
|
266
|
+
merge 5: {route-get-api-users} + {report-daily-users, table-users} -> Q = 0.405
|
|
267
|
+
merge 6: {screen-users} + {integration-billing-sync, route-get-api-users-id-welcome} -> Q = 0.485
|
|
268
|
+
merge 7: {route-post-api-users, screen-default} + {setting-welcome-email-enabled, workflow-signup-welcome} -> Q = 0.505
|
|
269
|
+
no further join raises Q; stopping
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
Final partition: `{job-nightly-digest, table-audit-log}`,
|
|
273
|
+
`{report-daily-users, route-get-api-users, table-users}`,
|
|
274
|
+
`{integration-billing-sync, route-get-api-users-id-welcome,
|
|
275
|
+
screen-users}`, `{route-post-api-users, screen-default,
|
|
276
|
+
setting-welcome-email-enabled, workflow-signup-welcome}`, at `Q = 0.505`.
|
|
277
|
+
`0.505 >= 0.3`: the refinement is what clears the floor here, not the
|
|
278
|
+
components-only figure, and merging whole components (the no-op the clause
|
|
279
|
+
above warns against) would only have found the wrong answer, not a
|
|
280
|
+
conservative one.
|
|
281
|
+
|
|
282
|
+
The four edgeless elements are still owed a home, per the rule above, and
|
|
283
|
+
the graph has nothing to say about any of them. Assigned by hand, each on
|
|
284
|
+
the source proximity the rule names, and recorded here because nothing
|
|
285
|
+
computes it:
|
|
286
|
+
|
|
287
|
+
- `route-default-aspx` joins `{route-post-api-users, screen-default,
|
|
288
|
+
setting-welcome-email-enabled, workflow-signup-welcome}`. It *is*
|
|
289
|
+
`Default.aspx`, and `screen-default` is the same file's rendered half.
|
|
290
|
+
- `route-users-aspx` joins `{integration-billing-sync,
|
|
291
|
+
route-get-api-users-id-welcome, screen-users}`, for the same reason
|
|
292
|
+
against `Users.aspx`.
|
|
293
|
+
- `setting-nightly-digest-cutoff-days` joins `{job-nightly-digest,
|
|
294
|
+
table-audit-log}`: `Jobs/NightlyDigestJob.cs:13` is the only line in the
|
|
295
|
+
source that reads it, and that job is the whole of that community.
|
|
296
|
+
- `setting-default-connection` joins nothing, deliberately, and is the
|
|
297
|
+
exception the rule names. `Controllers/UsersController.cs:17` and
|
|
298
|
+
`Jobs/NightlyDigestJob.cs:14` both read it, and those two lines serve
|
|
299
|
+
three of the four communities between them, so no capability has a claim
|
|
300
|
+
the others do not. It gets a queue item instead, and extract disposes of
|
|
301
|
+
it `out-of-scope` citing that item.
|
|
302
|
+
|
|
303
|
+
`scripts/__tests__/e2e-webforms.test.ts` drives exactly this partition,
|
|
304
|
+
these four assignments included, so a change to either has to be a change
|
|
305
|
+
to both.
|
|
306
|
+
|
|
307
|
+
**Evidence.** Every validator's script (the actual command or program run,
|
|
308
|
+
not a description of it) and its raw output goes into `.migrate/seam.md`,
|
|
309
|
+
verbatim, so a reviewer can retrace exactly what ran and what it found.
|
|
310
|
+
This is prose because it is an audit trail, not a count anything balances.
|
|
311
|
+
|
|
312
|
+
**Write the partition by hand.** There is no `seam` verb: nothing in the
|
|
313
|
+
CLI authors `capabilities.jsonl`, `seam.json`, or `seam.md`. All three are
|
|
314
|
+
hand-written, the same way `parity-basis.md` is in phase 0. (`migrate reset
|
|
315
|
+
--phase seam` does touch these paths, clearing `capabilities.jsonl` and
|
|
316
|
+
deleting the other two, but that undoes the phase rather than writing its
|
|
317
|
+
content.) Whichever validator produced the accepted partition (the worked
|
|
318
|
+
examples above show surface-affinity; a schema-clustering or call-graph
|
|
319
|
+
result is written the same way), each community becomes one line. The gate
|
|
320
|
+
checks the file for duplicate slugs (the only structural check it gets,
|
|
321
|
+
since there is no importer to validate it at write time) and later, in
|
|
322
|
+
extract, for every requirement's `cap` resolving to one of these slugs.
|
|
323
|
+
|
|
324
|
+
`seam.json`'s shape is in `docs/reference.md`'s store artifacts section.
|
|
325
|
+
Unlike `capabilities.jsonl`, no gate reads `seam.json` or `seam.md` at
|
|
326
|
+
all, not for duplicate slugs, not for anything: `check.ts` contains no
|
|
327
|
+
mention of either file. Both are trusted entirely on the strength of
|
|
328
|
+
whoever wrote them, the same as `parity-basis.md`.
|
|
329
|
+
|
|
330
|
+
## What closes it
|
|
331
|
+
|
|
332
|
+
There is no census kind for seam either: not a lens, an attribute, a
|
|
333
|
+
rule-sweep, or a closer. The phase closes on the three artifacts existing
|
|
334
|
+
and the status flip, the same shape as probe:
|
|
335
|
+
|
|
336
|
+
```
|
|
337
|
+
migrate phase seam --status done
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
`migrate phase seam --status done` succeeds even if `enumerate` is not
|
|
341
|
+
`done` yet: the status setter does not check its predecessor. `migrate
|
|
342
|
+
check` does. A real run against a store where `enumerate` was still
|
|
343
|
+
`running` when `seam` was flipped to `done` reported:
|
|
344
|
+
|
|
345
|
+
```
|
|
346
|
+
run-state:
|
|
347
|
+
phase enumerate is running; every phase through seam must be done
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
That is the mechanism behind `SKILL.md`'s "do not skip ahead": nothing
|
|
351
|
+
stops you from flipping phases out of order, but `check --phase seam` (or
|
|
352
|
+
later, plain `check`) names exactly which predecessor is not finished.
|
|
353
|
+
|
|
354
|
+
## Degradation
|
|
355
|
+
|
|
356
|
+
| Validator | Needs | If unavailable |
|
|
357
|
+
|---|---|---|
|
|
358
|
+
| Schema clustering | A parseable relational schema | Folds into the combined case below; the spec does not give schema clustering its own degradation separate from call-graph detection. |
|
|
359
|
+
| Call-graph community detection | Statically parseable code | Same combined case. |
|
|
360
|
+
| Change-coupling analysis | VCS history | Records `not-applicable` in `seam.md`, with the reason. The remaining validators must still reach two in agreement; change-coupling cannot be counted toward that two. |
|
|
361
|
+
| Surface-affinity clustering | Only the ledger's `refs` | No degradation case: it needs nothing that is ever absent once enumerate has produced a ledger, so it is always available. |
|
|
362
|
+
|
|
363
|
+
**Combined case: no parseable call graph and no relational schema.** If
|
|
364
|
+
VCS history still exists, change-coupling and surface-affinity are the two
|
|
365
|
+
left, and both must agree at Q >= 0.3. If VCS is also absent,
|
|
366
|
+
surface-affinity is the only validator that can run at all, and its own Q
|
|
367
|
+
against the 0.3 floor is the whole test (above). A real source with none
|
|
368
|
+
of the three (a COBOL system delivered as a flat-file export, no VCS)
|
|
369
|
+
escalates like this when that lone Q comes in low:
|
|
370
|
+
|
|
371
|
+
```markdown
|
|
372
|
+
---
|
|
373
|
+
id: q-seam-low-modularity
|
|
374
|
+
severity: critical
|
|
375
|
+
status: open
|
|
376
|
+
---
|
|
377
|
+
|
|
378
|
+
## Evidence
|
|
379
|
+
|
|
380
|
+
Neither a relational schema nor statically parseable code exists in this
|
|
381
|
+
source (COBOL delivered as a flat-file export, no VCS history). Surface
|
|
382
|
+
affinity clustering, the only validator that could run at all, produced
|
|
383
|
+
modularity Q = 0.21 on connected components; the largest component was
|
|
384
|
+
still large and not obviously one capability, so the greedy refinement
|
|
385
|
+
above was run against it too, and stopped at the same Q = 0.21, no join
|
|
386
|
+
raising it. That figure, not a components-only one, is below the 0.3
|
|
387
|
+
floor, and there is no second opinion available to triangulate against.
|
|
388
|
+
|
|
389
|
+
## Options
|
|
390
|
+
|
|
391
|
+
(a) Accept the low-Q surface-affinity partition anyway. (b) Re-run
|
|
392
|
+
surface-affinity clustering with a coarser resolution parameter and see if
|
|
393
|
+
Q improves. (c) Fall back to vertical-slice-only: one capability per user
|
|
394
|
+
journey, no cross-cutting split.
|
|
395
|
+
|
|
396
|
+
## Recommendation
|
|
397
|
+
|
|
398
|
+
Recommend (c), vertical-slice-only, since no validator reached the 0.3
|
|
399
|
+
floor and there is nothing else here to triangulate against.
|
|
400
|
+
```
|
|
401
|
+
|
|
402
|
+
`migrate queue add q-seam-low-modularity.md` accepts this file and prints
|
|
403
|
+
`queue add: q-seam-low-modularity [critical]`.
|
|
404
|
+
|
|
405
|
+
## Commands
|
|
406
|
+
|
|
407
|
+
```
|
|
408
|
+
migrate queue add <item.md>
|
|
409
|
+
migrate phase seam --status done
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
`queue add` runs only when triangulation disagrees or Q stays below 0.3;
|
|
413
|
+
`capabilities.jsonl`, `seam.json`, and `seam.md` are written by hand
|
|
414
|
+
regardless, with no command that authors their content (`migrate reset
|
|
415
|
+
--phase seam` clears `capabilities.jsonl` and deletes the other two; it
|
|
416
|
+
authors none of them).
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
# Recipe contract
|
|
2
|
+
|
|
3
|
+
A recipe answers one question for one source stack: what are at least two
|
|
4
|
+
independent directions for enumerating each declared surface, and what probe
|
|
5
|
+
realises each one. That is the whole contract.
|
|
6
|
+
|
|
7
|
+
> A recipe supplies, per surface type in `[surfaces].types`, at least two
|
|
8
|
+
> independent directions and the probes that realise them. Nothing else.
|
|
9
|
+
|
|
10
|
+
The lens contract itself, what a direction is, how directions get deduped
|
|
11
|
+
and diffed against the ledger, how a census record closes a surface, is
|
|
12
|
+
stated once in `references/phases/enumerate.md` and is not repeated here. A
|
|
13
|
+
recipe is an input to that contract, not a second copy of it.
|
|
14
|
+
|
|
15
|
+
## What a recipe does not do
|
|
16
|
+
|
|
17
|
+
- **It does not restate the lens contract.** No prose here should explain
|
|
18
|
+
what enumerating from two directions means, what `evidence` is for, or how
|
|
19
|
+
a census record balances. That is `enumerate.md`'s job.
|
|
20
|
+
- **It does not carry classification rules.** Deciding **add**, **skip with
|
|
21
|
+
a named reason**, or **queue** for something a probe turns up is the
|
|
22
|
+
enumerating agent's judgment call against this specific source, not
|
|
23
|
+
something a recipe file can decide in advance for every source in a stack
|
|
24
|
+
family.
|
|
25
|
+
- **It does not gate anything.** A recipe cannot make `migrate check` pass or
|
|
26
|
+
fail. The census does that, identically whether its directions came from a
|
|
27
|
+
recipe or were derived by hand in contract-only mode.
|
|
28
|
+
|
|
29
|
+
## Selection
|
|
30
|
+
|
|
31
|
+
`[source].stack` is written once, in phase 0 (`references/phases/probe.md`),
|
|
32
|
+
by `migrate init`. It is not detected: `init` detects exactly one thing, `vcs`,
|
|
33
|
+
from whether the source has a `.git` directory. `stack` is whatever
|
|
34
|
+
`--source-stack` was passed, and `unknown` when it was not. Detecting the
|
|
35
|
+
stack is the probing agent's own job, per `probe.md`'s Procedure step 1, which
|
|
36
|
+
is also where `unknown` is stated to be a valid answer rather than a failure.
|
|
37
|
+
Enumerate reads the value back (`enumerate.md`'s Inputs section) to decide
|
|
38
|
+
which file in this directory, if any, names this run's directions.
|
|
39
|
+
|
|
40
|
+
The convention is one file per stack family, named `<family>.md`. A stack
|
|
41
|
+
value that names or clearly belongs to a family with a file here
|
|
42
|
+
(`aspnet-webforms`, `aspnet-mvc`, and `aspnet-webapi` all resolve to
|
|
43
|
+
`aspnet.md`) uses that file's directions. Nothing in the CLI performs this
|
|
44
|
+
lookup: it is a read the enumerating agent does for itself, the same way it
|
|
45
|
+
reads any other file `enumerate.md` names as an input.
|
|
46
|
+
|
|
47
|
+
**No file in this directory matches the detected stack.** That is
|
|
48
|
+
contract-only mode, described in `enumerate.md`'s "Contract-only mode"
|
|
49
|
+
section: the agent derives its own two directions per surface, and the
|
|
50
|
+
census gates them exactly as it would a recipe's. This is a supported path,
|
|
51
|
+
not a degraded one. An unmatched stack is evidence the source needs a
|
|
52
|
+
recipe eventually, not evidence the run cannot proceed today.
|
|
53
|
+
|
|
54
|
+
## Adding a stack
|
|
55
|
+
|
|
56
|
+
Adding support for a new stack is one new file in this directory, written to
|
|
57
|
+
the shape below, and no edit to any other file in this skill. `SKILL.md`,
|
|
58
|
+
the phase manuals, and the CLI do not name individual stacks anywhere; they
|
|
59
|
+
only read `[source].stack` and look in this directory. This is deliberate:
|
|
60
|
+
it is the property that keeps the recipe surface from becoming a second
|
|
61
|
+
place where classification rules or gating logic could leak in, and it is why
|
|
62
|
+
`aspnet.md` ships alongside this file rather than a longer list of
|
|
63
|
+
lightly-tested packs for stacks nobody has run a real migration against yet.
|
|
64
|
+
|
|
65
|
+
## File shape
|
|
66
|
+
|
|
67
|
+
One `#` title naming the stack. One `##` heading per surface, spelled
|
|
68
|
+
exactly as it appears in `[surfaces].types`. Under each heading, a bullet
|
|
69
|
+
list of at least two directions, each one line naming what the direction
|
|
70
|
+
reads and a `Probe:` line giving the literal command that reads it.
|
|
71
|
+
|
|
72
|
+
Below is `settings` and `workflows`, unmodified from `aspnet.md`, as the
|
|
73
|
+
shape to copy for a new stack: replace the direction names, descriptions,
|
|
74
|
+
and probes with the ones that actually apply, keep the two-line-per-direction
|
|
75
|
+
structure.
|
|
76
|
+
|
|
77
|
+
```markdown
|
|
78
|
+
## settings
|
|
79
|
+
|
|
80
|
+
- **Storage**: `appSettings` and `connectionStrings` entries in
|
|
81
|
+
`web.config`.
|
|
82
|
+
Probe: `rg -n -g '*.config' '<add (key|name)=' <source>`
|
|
83
|
+
- **Read sites**: `ConfigurationManager.AppSettings[...]` and
|
|
84
|
+
`ConfigurationManager.ConnectionStrings[...]` reads in code, which can name
|
|
85
|
+
a key `web.config` never declares (an environment-variable override, a key
|
|
86
|
+
added only at deploy time).
|
|
87
|
+
Probe: `rg -n -g '*.cs' 'ConfigurationManager\.(AppSettings|ConnectionStrings)' <source>`
|
|
88
|
+
|
|
89
|
+
## workflows
|
|
90
|
+
|
|
91
|
+
- **Multi-step controller flow (code)**: a wizard-shaped controller with
|
|
92
|
+
sequentially named actions (`Step1`, `Step2`, ...).
|
|
93
|
+
Probe: `rg -n -g '*.cs' 'ActionResult Step[0-9]+' <source>`
|
|
94
|
+
- **State carriers (code)**: `Session[...]` and `TempData[...]` reads and
|
|
95
|
+
writes tying those steps together, independent of how the actions
|
|
96
|
+
themselves happen to be named.
|
|
97
|
+
Probe: `rg -n -g '*.cs' 'Session\[|TempData\[' <source>`
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
Every probe in `aspnet.md` was run against four throwaway ASP.NET-shaped
|
|
101
|
+
trees, built independently of each other across three review rounds while it
|
|
102
|
+
was written, so this excerpt is proven content, not an invented template. All
|
|
103
|
+
twenty-one of its directions have since been run against a committed fixture
|
|
104
|
+
too, `fixtures/tiny-webforms`, by `scripts/__tests__/e2e-webforms.test.ts`,
|
|
105
|
+
which records each direction's count and the recipe's own probe command as
|
|
106
|
+
that count's evidence.
|
|
107
|
+
|
|
108
|
+
## Probes are a starting point
|
|
109
|
+
|
|
110
|
+
A probe here is a concrete command that worked against one real or
|
|
111
|
+
representative source. It is not a fixed script: a pattern that matches
|
|
112
|
+
nothing on a given checkout because that checkout names things differently
|
|
113
|
+
is not a broken probe, it is a signal to adapt the pattern. What actually
|
|
114
|
+
gates completeness is the surface's lens census in `enumerate.md`, run
|
|
115
|
+
against whatever the adapted probes turned up, not agreement with what is
|
|
116
|
+
written here.
|