@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.
Files changed (148) hide show
  1. package/README.md +18 -2
  2. package/dist/cli/index.js +105 -28
  3. package/package.json +1 -1
  4. package/skills/index.json +14 -2
  5. package/skills/magpie/README.md +2 -1
  6. package/skills/magpie/SKILL.md +138 -45
  7. package/skills/magpie/bin/magpie.ts +43 -0
  8. package/skills/magpie/fixtures/example-pr/brief.json +18 -0
  9. package/skills/magpie/fixtures/fake-gh-nodiff.sh +38 -0
  10. package/skills/magpie/fixtures/fake-gh.sh +14 -0
  11. package/skills/magpie/package.json +1 -1
  12. package/skills/magpie/references/peer-review.md +7 -2
  13. package/skills/magpie/references/scout.md +90 -0
  14. package/skills/magpie/references/specialists.md +117 -6
  15. package/skills/magpie/scripts/__tests__/cli.test.ts +101 -1
  16. package/skills/magpie/scripts/__tests__/dedupe-cmd.test.ts +187 -0
  17. package/skills/magpie/scripts/__tests__/diff-chunks.test.ts +51 -0
  18. package/skills/magpie/scripts/__tests__/filter-diff-preservation.test.ts +54 -0
  19. package/skills/magpie/scripts/__tests__/findings-files.test.ts +35 -0
  20. package/skills/magpie/scripts/__tests__/gh.test.ts +90 -0
  21. package/skills/magpie/scripts/__tests__/git-diff.test.ts +83 -0
  22. package/skills/magpie/scripts/__tests__/helpers/git-fixture.ts +47 -0
  23. package/skills/magpie/scripts/__tests__/path-filter.test.ts +27 -0
  24. package/skills/magpie/scripts/__tests__/preview-cmd.test.ts +16 -0
  25. package/skills/magpie/scripts/__tests__/refresh.test.ts +45 -0
  26. package/skills/magpie/scripts/__tests__/render-cmd.test.ts +173 -1
  27. package/skills/magpie/scripts/__tests__/render-findings.test.ts +151 -1
  28. package/skills/magpie/scripts/__tests__/render-progress.test.ts +42 -0
  29. package/skills/magpie/scripts/__tests__/setup-cmd.test.ts +83 -1
  30. package/skills/magpie/scripts/__tests__/shard.test.ts +165 -0
  31. package/skills/magpie/scripts/__tests__/skill-lint.test.ts +207 -7
  32. package/skills/magpie/scripts/__tests__/types.test.ts +47 -0
  33. package/skills/magpie/scripts/dedupe-cmd.ts +58 -3
  34. package/skills/magpie/scripts/diff-chunks.ts +28 -0
  35. package/skills/magpie/scripts/findings-files.ts +32 -0
  36. package/skills/magpie/scripts/gh.ts +68 -14
  37. package/skills/magpie/scripts/git-diff.ts +111 -0
  38. package/skills/magpie/scripts/path-filter.ts +9 -5
  39. package/skills/magpie/scripts/preview-cmd.ts +11 -1
  40. package/skills/magpie/scripts/refresh.ts +32 -3
  41. package/skills/magpie/scripts/render-cmd.ts +35 -11
  42. package/skills/magpie/scripts/render-findings.ts +78 -2
  43. package/skills/magpie/scripts/render-progress.ts +6 -1
  44. package/skills/magpie/scripts/setup-cmd.ts +38 -1
  45. package/skills/magpie/scripts/shard.ts +171 -0
  46. package/skills/magpie/scripts/status-cmd.ts +7 -4
  47. package/skills/magpie/scripts/types.ts +50 -0
  48. package/skills/magpie/skill.json +2 -2
  49. package/skills/magpie/templates/styles.css +66 -0
  50. package/skills/migrate/README.md +194 -0
  51. package/skills/migrate/SKILL.md +197 -0
  52. package/skills/migrate/bin/migrate +15 -0
  53. package/skills/migrate/bin/migrate.ts +309 -0
  54. package/skills/migrate/biome.json +35 -0
  55. package/skills/migrate/bun.lock +24 -0
  56. package/skills/migrate/docs/architecture.md +294 -0
  57. package/skills/migrate/docs/reference.md +590 -0
  58. package/skills/migrate/fixtures/tiny-express/GROUND-TRUTH.md +39 -0
  59. package/skills/migrate/fixtures/tiny-express/app.js +29 -0
  60. package/skills/migrate/fixtures/tiny-express/cron.js +6 -0
  61. package/skills/migrate/fixtures/tiny-express/reports/daily-users.json +6 -0
  62. package/skills/migrate/fixtures/tiny-express/schema.sql +12 -0
  63. package/skills/migrate/fixtures/tiny-express/settings.json +4 -0
  64. package/skills/migrate/fixtures/tiny-express/views/users.html +9 -0
  65. package/skills/migrate/fixtures/tiny-webforms/Controllers/UsersController.cs +68 -0
  66. package/skills/migrate/fixtures/tiny-webforms/Default.aspx +7 -0
  67. package/skills/migrate/fixtures/tiny-webforms/Default.aspx.cs +14 -0
  68. package/skills/migrate/fixtures/tiny-webforms/GROUND-TRUTH.md +50 -0
  69. package/skills/migrate/fixtures/tiny-webforms/Integrations/BillingClient.cs +16 -0
  70. package/skills/migrate/fixtures/tiny-webforms/Jobs/NightlyDigestJob.cs +33 -0
  71. package/skills/migrate/fixtures/tiny-webforms/Reports/DailyUsers.rdl +11 -0
  72. package/skills/migrate/fixtures/tiny-webforms/Schema.sql +12 -0
  73. package/skills/migrate/fixtures/tiny-webforms/Site.master +16 -0
  74. package/skills/migrate/fixtures/tiny-webforms/Users.aspx +8 -0
  75. package/skills/migrate/fixtures/tiny-webforms/Users.aspx.cs +14 -0
  76. package/skills/migrate/fixtures/tiny-webforms/web.config +10 -0
  77. package/skills/migrate/install.sh +68 -0
  78. package/skills/migrate/package.json +17 -0
  79. package/skills/migrate/references/phases/enumerate.md +291 -0
  80. package/skills/migrate/references/phases/extract.md +652 -0
  81. package/skills/migrate/references/phases/parity.md +275 -0
  82. package/skills/migrate/references/phases/probe.md +135 -0
  83. package/skills/migrate/references/phases/queue.md +242 -0
  84. package/skills/migrate/references/phases/seam.md +416 -0
  85. package/skills/migrate/references/recipes/README.md +116 -0
  86. package/skills/migrate/references/recipes/aspnet.md +287 -0
  87. package/skills/migrate/references/run-ops.md +280 -0
  88. package/skills/migrate/scripts/__tests__/census.test.ts +775 -0
  89. package/skills/migrate/scripts/__tests__/check.test.ts +458 -0
  90. package/skills/migrate/scripts/__tests__/citations.test.ts +156 -0
  91. package/skills/migrate/scripts/__tests__/cli.test.ts +183 -0
  92. package/skills/migrate/scripts/__tests__/concurrency.test.ts +164 -0
  93. package/skills/migrate/scripts/__tests__/config.test.ts +112 -0
  94. package/skills/migrate/scripts/__tests__/e2e-express.test.ts +1093 -0
  95. package/skills/migrate/scripts/__tests__/e2e-webforms.test.ts +1276 -0
  96. package/skills/migrate/scripts/__tests__/e2e.test.ts +320 -0
  97. package/skills/migrate/scripts/__tests__/ids.test.ts +38 -0
  98. package/skills/migrate/scripts/__tests__/import.test.ts +155 -0
  99. package/skills/migrate/scripts/__tests__/init.test.ts +192 -0
  100. package/skills/migrate/scripts/__tests__/leaks.test.ts +176 -0
  101. package/skills/migrate/scripts/__tests__/lock.test.ts +183 -0
  102. package/skills/migrate/scripts/__tests__/paths.test.ts +129 -0
  103. package/skills/migrate/scripts/__tests__/phase-cmd.test.ts +151 -0
  104. package/skills/migrate/scripts/__tests__/phases.test.ts +70 -0
  105. package/skills/migrate/scripts/__tests__/queue.test.ts +475 -0
  106. package/skills/migrate/scripts/__tests__/report.test.ts +150 -0
  107. package/skills/migrate/scripts/__tests__/run-state.test.ts +136 -0
  108. package/skills/migrate/scripts/__tests__/status-reset.test.ts +318 -0
  109. package/skills/migrate/scripts/__tests__/store.test.ts +132 -0
  110. package/skills/migrate/scripts/__tests__/validate.test.ts +54 -0
  111. package/skills/migrate/scripts/census-cmd.ts +109 -0
  112. package/skills/migrate/scripts/census.ts +342 -0
  113. package/skills/migrate/scripts/check-cmd.ts +24 -0
  114. package/skills/migrate/scripts/check.ts +376 -0
  115. package/skills/migrate/scripts/citations.ts +92 -0
  116. package/skills/migrate/scripts/config.ts +237 -0
  117. package/skills/migrate/scripts/ids.ts +31 -0
  118. package/skills/migrate/scripts/import-cmd.ts +141 -0
  119. package/skills/migrate/scripts/init-cmd.ts +118 -0
  120. package/skills/migrate/scripts/leaks.ts +184 -0
  121. package/skills/migrate/scripts/lock.ts +188 -0
  122. package/skills/migrate/scripts/paths.ts +103 -0
  123. package/skills/migrate/scripts/phase-cmd.ts +63 -0
  124. package/skills/migrate/scripts/phases.ts +113 -0
  125. package/skills/migrate/scripts/queue-cmd.ts +98 -0
  126. package/skills/migrate/scripts/queue.ts +258 -0
  127. package/skills/migrate/scripts/report-cmd.ts +47 -0
  128. package/skills/migrate/scripts/report.ts +131 -0
  129. package/skills/migrate/scripts/reset-cmd.ts +120 -0
  130. package/skills/migrate/scripts/status-cmd.ts +52 -0
  131. package/skills/migrate/scripts/store.ts +159 -0
  132. package/skills/migrate/scripts/types.ts +137 -0
  133. package/skills/migrate/scripts/validate.ts +221 -0
  134. package/skills/migrate/skill.json +33 -0
  135. package/skills/migrate/templates/config.toml +27 -0
  136. package/skills/migrate/templates/queue-item.md +17 -0
  137. package/skills/migrate/tsconfig.json +18 -0
  138. package/skills/migrate/uninstall.sh +31 -0
  139. package/skills/sluice/SKILL.md +82 -0
  140. package/skills/sluice/references/deep-channel.md +94 -0
  141. package/skills/sluice/references/finish.md +35 -0
  142. package/skills/sluice/references/intent.md +29 -0
  143. package/skills/sluice/references/review.md +42 -0
  144. package/skills/sluice/references/root-cause.md +38 -0
  145. package/skills/sluice/references/show-or-say.md +36 -0
  146. package/skills/sluice/references/test-first.md +35 -0
  147. package/skills/sluice/references/verify.md +26 -0
  148. 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.