@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,287 @@
1
+ # aspnet
2
+
3
+ Covers `[source].stack` values in the ASP.NET / .NET Framework family:
4
+ `aspnet-webforms`, `aspnet-mvc`, `aspnet-webapi`, and `dotnet-framework`. Real
5
+ ASP.NET sources routinely mix WebForms, MVC, and Web API in one checkout (a
6
+ WebForms app growing a Web API layer for AJAX calls is the common case), so
7
+ several surfaces below name more than the two-direction floor. That
8
+ granularity is extracted from a private plugin's own lens recipes that ran a
9
+ real migration campaign, where this mixing was the norm rather than the
10
+ exception.
11
+
12
+ Every probe uses `rg` (ripgrep). If it is not installed, `grep -rnE
13
+ --include='*.ext' '<pattern>' <path>` finds the same matches on real BSD
14
+ `grep`; the one place the exact pattern text has to differ between the two
15
+ tools is noted where it occurs (tables' raw-ADO probe). Replace `<source>`
16
+ with the checkout root. These are starting points, not a fixed script:
17
+ adapt the glob and the pattern to how a real source actually lays things
18
+ out, and the surface's lens census is what decides whether the result is
19
+ complete, not this file. See `references/recipes/README.md` for the
20
+ contract this file fills in, and `references/phases/enumerate.md` for the
21
+ contract itself.
22
+
23
+ Every probe below was run against four throwaway ASP.NET-shaped trees,
24
+ built independently of each other across three review rounds (different
25
+ directory layouts, different
26
+ C# formatting, different edge cases in the same construct, including
27
+ partial-class controllers, multi-part and bracket-quoted SQL identifiers,
28
+ and WebForms master pages) before being written down. Where a probe missed
29
+ something planted in one of those trees, or, worse, returned a name that
30
+ did not correspond to a real element, the probe was fixed and every tree
31
+ was re-checked; where a gap cannot be closed (noted per-direction below),
32
+ it is stated rather than the claim weakened around it. Independent review
33
+ against trees built without sight of these fixtures has twice found gaps
34
+ these trees did not exercise; each is folded into the fixes and disclosures
35
+ below rather than only into this file's revision history. Every direction
36
+ below has since been run against a committed fixture as well,
37
+ `fixtures/tiny-webforms`, by `scripts/__tests__/e2e-webforms.test.ts`, which
38
+ records each one's count with the probe command beside it as that count's
39
+ evidence, zero-finding directions included.
40
+
41
+ ## routes
42
+
43
+ - **Attribute routing (code)**: HTTP-verb attributes (`[HttpGet]`,
44
+ `[HttpPost]`, ...), `[Route(...)]`, and `[RoutePrefix(...)]`, in any of
45
+ their C#-legal forms: bare (`[HttpGet]`, no parens), comma-combined with
46
+ another attribute in the same brackets (`[HttpPost, Route("")]`), or
47
+ spaced inside the brackets (`[ Route(...) ]`, `[Route (...)]`). A regex
48
+ anchored to `\(` right after the attribute name, an earlier version of
49
+ this probe, misses all of these except the plain parenthesised form.
50
+ Probe: `rg -n -g '*.cs' '[\[,]\s*(RoutePrefix|Route|HttpGet|HttpPost|HttpPut|HttpDelete|HttpPatch|HttpHead|HttpOptions)\b' <source>`
51
+ Known gap: this also matches the same words inside a comment or a string
52
+ (`// see [Route] above`), since the probe reads text, not parsed syntax.
53
+ That is a classification-time false positive for the enumerating agent to
54
+ skip, not something the regex can rule out.
55
+ Known gap, overcounting (RoutePrefix): a class-level `[RoutePrefix("...")]`
56
+ line matches too, and it annotates no action at all: it is a prefix
57
+ applied to every action underneath it, not a route of its own. Exclude it
58
+ entirely before counting anything; it is not a case of one action
59
+ matched twice, it is a line that never named an action to begin with.
60
+ This differs from convention routing's disclosed `MapRoute`/
61
+ `MapHttpRoute` gap in one important way: `MapRoute` is a separately
62
+ labelled supporting probe, run outside the regex this direction counts,
63
+ so nothing about it was ever in the tally to subtract from. `RoutePrefix`
64
+ is matched by the very regex this direction counts, so it has to be
65
+ subtracted from that count directly, a stronger and easier-to-miss
66
+ burden on the reader than a probe whose result was never combined with
67
+ this one's in the first place.
68
+ Known gap, overcounting (split attributes): the verb attribute and the
69
+ `Route` attribute frequently sit on separate lines rather than combined
70
+ in one bracket (`[HttpGet]` on one line, `[Route("...")]` on the next),
71
+ and both lines match, naming the same action twice. Run against a
72
+ controller with one `RoutePrefix` and three actions, each written with
73
+ its verb and `Route` attributes on separate lines, this probe returns
74
+ seven matched lines: one `RoutePrefix` plus two lines per action.
75
+ Excluding the `RoutePrefix` line (the paragraph above) leaves six;
76
+ deduplicating each action's two lines down to the one action they both
77
+ annotate leaves three, the count this direction reports, the same unit
78
+ convention routing counts, not the raw seven matched lines.
79
+ - **Convention routing (code)**: public action methods on a class whose name
80
+ contains `Controller`. A convention-routed action carries no attribute at
81
+ all, so the registration statement is not a stand-in for it: counting
82
+ `MapRoute` / `MapHttpRoute` calls instead of actions undercounts by orders
83
+ of magnitude, because one registration statement can service every
84
+ conventionally-routed action in the project. The count this direction
85
+ reports has to be actions, since that is the unit `routes` is counting.
86
+ The glob is `*Controller*.cs`, not the narrower `*Controller.cs`: a
87
+ partial class split across `ReportsController.cs` and
88
+ `ReportsController.Extra.cs` has actions in both files, and the narrower
89
+ glob never reaches the second one, which undercounts in exactly the way
90
+ that breaks `total <= sum(directions)` when nothing else is there to mask
91
+ it.
92
+ Probe: `rg -n -g '*Controller*.cs' 'public\s+\S+\s+\w+\s*\(' <source>`
93
+ Supporting evidence, not the count: `rg -n -g '*.cs' '\.MapRoute\(|MapHttpRoute\(' <source>`
94
+ confirms a conventional route table is actually registered, which is
95
+ worth knowing even though it does not say how many actions it serves.
96
+ Known gap, overlap: this probe's matches overlap the attribute-routing
97
+ direction above, because an attribute-routed action is still a public
98
+ method in a `*Controller*.cs` file. The two directions are not
99
+ independent counts of disjoint evidence; they are two ways of finding
100
+ some of the same actions plus each other's blind spots (attributes this
101
+ probe cannot see are named here, and conventionally-routed actions the
102
+ other probe cannot see are named there). Do not sum the two raw counts
103
+ into `total`: dedupe by action identity (class plus method, or the
104
+ resolved URL) the same way `enumerate.md`'s merge step already requires
105
+ whenever two directions both name the same real element, and record the
106
+ deduped count. Known gap, false positives: the widened glob also matches
107
+ a file like `UsersControllerTests.cs`, and the pattern itself matches a
108
+ comment or string shaped like a method signature, the same class of
109
+ false positive the attribute-routing direction discloses above; both are
110
+ classification-time calls for the enumerating agent, not something the
111
+ probe can rule out.
112
+ - **WebForms filesystem**: a `.aspx` page or `.ashx` handler is its own route
113
+ by virtue of existing on disk; there is no registration to grep for.
114
+ Probe: `find <source> -type f \( -name '*.aspx' -o -name '*.ashx' \)`
115
+
116
+ ## tables
117
+
118
+ - **DDL and migrations**: `CREATE TABLE` statements in `.sql` scripts, and
119
+ the migration-builder calls that create a table without ever emitting
120
+ that literal text (EF Migrations' `migrationBuilder.CreateTable(`,
121
+ FluentMigrator's `Create.Table(`). A table defined only through a
122
+ migration and never in a static `.sql` file is invisible to the first
123
+ probe alone; the second is what catches it.
124
+ Probe: `rg -n -g '*.sql' -i '^\s*CREATE TABLE' <source>`
125
+ Probe: `rg -n -g '*.cs' 'migrationBuilder\.CreateTable\(|Create\.Table\(' <source>`
126
+ - **ORM**: EF `DbSet<T>` properties on a `DbContext`, and `[Table("...")]`
127
+ attributes on the mapped classes.
128
+ Probe: `rg -n -g '*.cs' 'DbSet<|\[Table\(' <source>`
129
+ - **Raw ADO command text**: table names embedded in `SqlCommand` /
130
+ `CommandText` strings, for code that never goes through the ORM at all.
131
+ This probe does not extract a name at all: an earlier version stripped the
132
+ match down to a single capture group (`-o -r '$2'`), and that convenience
133
+ is what let it fabricate a table name that never existed. A stray `UPDATE`
134
+ or `FROM` inside a comment (`// ... no FROM/INTO/UPDATE verb in the
135
+ command`) extracted the next bare word as if it were a real identifier,
136
+ and a three-part name (`FROM ReportingServer.dbo.Orders`) resolved to the
137
+ middle qualifier (`dbo`) instead of the table, the same bug Finding 4
138
+ closed one level down. A fabricated name is worse than a miss: it enters
139
+ the ledger looking exactly like a real one, and nothing downstream can
140
+ tell the difference. So this probe prints the whole matched line instead,
141
+ and a person or agent reads the actual table name out of it rather than
142
+ trusting an auto-extracted token.
143
+ Probe: `rg -n -g '*.cs' '(FROM|INTO|UPDATE)\s+((\[[^]]+\]|\w+)\.)*(\[[^]]+\]|\w+)' <source> | grep -Ev '^[^:]+:[0-9]+:[[:space:]]*//'`
144
+ The pattern itself also had to change to stop under-matching: `\w+` alone
145
+ cannot start on a bracket, so `UPDATE [dbo].[Invoices]`, the ordinary
146
+ SQL Server style for tooling-generated code, matched nothing at all
147
+ before this fix. The alternation `\[[^]]+\]|\w+` accepts either a
148
+ bracket-quoted segment or a plain identifier for every part of the name,
149
+ and `(...)*` repeats that for as many dot-separated qualifiers as the
150
+ source actually has (schema, database, linked server, or more), not just
151
+ one. The trailing `grep -Ev` drops a line whose first non-space character
152
+ is `//`, which is what would have caught the fabrication case above, and
153
+ nothing else: a trailing `// ...` after real code on the same line
154
+ survives, and so does any `/* ... */` block comment, whether it is a
155
+ standalone line or a continuation line inside a multi-line block (a line
156
+ starting with ` * `, the ordinary XML-doc and block-comment continuation
157
+ style, has no `//` at all and is not touched by this filter). Verified
158
+ directly: both a one-line `/* ... */` mentioning `UPDATE` and a
159
+ continuation line reading `* Continuation line mentioning UPDATE
160
+ dbo.Invoices...` inside a multi-line `/* */` block both pass through
161
+ unfiltered, each shown as its own real, unmodified line. That does not
162
+ reopen the fabrication this probe was fixed to close, since the full line
163
+ is still what is shown, but it is a hit a reader has to recognise as a
164
+ comment rather than one the pipeline discards for them.
165
+ Known gap, precisely: bracket-quoted (`[dbo].[Invoices]`) and plain
166
+ identifiers are handled, with any number of dot-separated qualifier
167
+ segments. Double-quoted ANSI identifiers (`"dbo"."Invoices"`) are not.
168
+ SQL-looking text inside an ordinary, verbatim, or interpolated string
169
+ literal that is not actually a `SqlCommand` argument (a log message, a
170
+ test fixture string, a piece of sample data) also matches and is shown as
171
+ a hit; it is an honest line, not a fabricated name, but a reader
172
+ classifying output will meet it and should expect to.
173
+ Stored-procedure calls (`SqlCommand("EXEC dbo.RecalculateLeadScore",
174
+ conn)`, or a `CommandType.StoredProcedure` command with the name set
175
+ separately) name a procedure, not a table, and carry none of the
176
+ `SELECT`/`INSERT`/`UPDATE` verb shapes this probe looks for, so they are
177
+ invisible to it by construction, not by an oversight this probe could
178
+ close. A table name built by string interpolation or concatenation
179
+ (`FROM {tableName}`, `FROM " + tbl`) is the same kind of construction gap:
180
+ the identifier a probe would need to report is not in the source text at
181
+ all. `rg -n -g '*.cs' '(FROM|INTO|UPDATE)\s+\{' <source>` at least flags
182
+ that call site for a person to trace by hand; it does not name the table.
183
+ The `grep -rE --include` fallback for the primary probe above matches
184
+ identically on real BSD `grep`, with one substitution: BSD grep does not
185
+ accept `\]` as an escaped literal inside a negated bracket expression
186
+ (`[^\]]` silently matches nothing, with no error message), so the class
187
+ must be written `[^]]`, with the `]` placed first, which both `rg` and BSD
188
+ `grep` accept the same way.
189
+
190
+ ## jobs
191
+
192
+ - **Quartz.NET**: `IJob` implementations and `JobBuilder` / `ScheduleJob`
193
+ registrations.
194
+ Probe: `rg -n -g '*.cs' 'IJob\b|JobBuilder\.Create|ScheduleJob\(' <source>`
195
+ - **Hangfire**: `RecurringJob` and `BackgroundJob` call sites.
196
+ Probe: `rg -n -g '*.cs' 'RecurringJob\.|BackgroundJob\.' <source>`
197
+ Known gap: a job registered through DI (`services.AddHangfire(...)`,
198
+ an injected `IRecurringJobManager`'s `.AddOrUpdate(...)`) never calls the
199
+ static `RecurringJob`/`BackgroundJob` methods this probe looks for, so it
200
+ is invisible to a static-call-only probe. A separate probe for
201
+ `AddHangfire\(|IRecurringJobManager\b` would find the registration site,
202
+ not the job itself, and this recipe does not attempt that second probe.
203
+ - **OS/config scheduling**: exported Windows Task Scheduler XML, and
204
+ scheduling-flavored `web.config` `appSettings` entries (a cron string, an
205
+ interval key), for jobs that were never wired through a .NET scheduler
206
+ library at all.
207
+ Probe: `rg -n -g '*.xml' -g '*.config' -i 'cron|schedule|<Task version' <source>`
208
+
209
+ ## reports
210
+
211
+ - **Definitions on disk**: SSRS `.rdl` and `.rdlc` files.
212
+ Probe: `find <source> -type f \( -name '*.rdl' -o -name '*.rdlc' \)`
213
+ - **Menu and navigation entries**: report links in a site map, a Razor
214
+ layout, a WebForms master page, or a nav/menu partial. Scoping to
215
+ `*.sitemap` alone misses an MVC application's ordinary navigation, which
216
+ lives in `_Layout.cshtml`, and misses a WebForms application's, which
217
+ lives in a `.master` page; a checkout mixing WebForms and MVC, which this
218
+ recipe's own preamble says is the common case, needs all of these globs,
219
+ not just the sitemap. These can also name a report the checkout no
220
+ longer ships an `.rdl` for.
221
+ Probe: `rg -n -g '*.sitemap' -g '_Layout.cshtml' -g '*.master' -g '*Nav*.cshtml' -g '*Menu*.cshtml' -i 'report' <source>`
222
+ Known gap: a menu built entirely from a database table (a `Menus` or
223
+ `NavigationItems` row set rendered at runtime, with no report name ever
224
+ written into any file this probe reads) is unreachable by any grep
225
+ probe, the same class of gap as the string-interpolated table name
226
+ disclosed under `tables` above: the identifier does not exist in the
227
+ source text.
228
+ - **Report-builder registrations**: a catalog class or config section that
229
+ maps a report key to its `.rdl` / `.rdlc` path, independent of both
230
+ directions above.
231
+ Probe: `rg -n -g '*.cs' '\.rdlc?"' <source>`
232
+
233
+ ## screens
234
+
235
+ - **Filesystem**: `.aspx` pages (WebForms) and `.cshtml` views (MVC). A
236
+ shared layout or partial (`_Layout.cshtml`) matches the same glob; telling
237
+ it apart from an actual screen is the enumerating agent's classification
238
+ call, not something a filename pattern can make for it.
239
+ Probe: `find <source> -type f \( -name '*.aspx' -o -name '*.cshtml' \)`
240
+ - **Sitemap/nav registration**: entries in `Web.sitemap`, a Razor layout, a
241
+ WebForms master page, or a nav/menu partial. Same scoping gap as the
242
+ reports direction above, in both dimensions: an MVC application's real
243
+ navigation usually lives in `_Layout.cshtml`, and a WebForms
244
+ application's in a `.master` page, neither a sitemap; the content pattern
245
+ also has to cover a plain WebForms anchor (`href="~/..."`) or server nav
246
+ control (`NavigateUrl="~/..."`), not only the sitemap's XML `url=`
247
+ attribute or MVC's `Html.ActionLink`/`Url.Action`, since a `.master` page
248
+ almost never uses the latter two.
249
+ Probe: `rg -n -g '*.sitemap' -g '_Layout.cshtml' -g '*.master' -g '*Nav*.cshtml' -g '*Menu*.cshtml' '(url|href|NavigateUrl)="~/|Html\.ActionLink\(|Url\.Action\(' <source>`
250
+ Known gap: the same database-driven menu gap disclosed under `reports`
251
+ above applies here identically, since both directions read the same
252
+ files for the same reason.
253
+
254
+ ## integrations
255
+
256
+ - **Outbound HTTP call sites (code)**: `HttpClient` and `WebClient`
257
+ construction.
258
+ Probe: `rg -n -g '*.cs' 'new HttpClient\(|new WebClient\(' <source>`
259
+ - **WCF service references (code)**: `ClientBase<T>` / `ChannelFactory<T>`
260
+ proxies and `[ServiceContract]` interfaces.
261
+ Probe: `rg -n -g '*.cs' 'ClientBase<|ChannelFactory<|\[ServiceContract\]' <source>`
262
+ - **Configured endpoints**: `<endpoint>` entries under
263
+ `<system.serviceModel><client>` in `web.config`, which can name an
264
+ integration no call site in code constructs directly (a proxy generated
265
+ entirely from config at runtime).
266
+ Probe: `rg -n -g '*.config' '<endpoint\b' <source>`
267
+
268
+ ## workflows
269
+
270
+ - **Multi-step controller flow (code)**: a wizard-shaped controller with
271
+ sequentially named actions (`Step1`, `Step2`, ...).
272
+ Probe: `rg -n -g '*.cs' 'ActionResult Step[0-9]+' <source>`
273
+ - **State carriers (code)**: `Session[...]` and `TempData[...]` reads and
274
+ writes tying those steps together, independent of how the actions
275
+ themselves happen to be named.
276
+ Probe: `rg -n -g '*.cs' 'Session\[|TempData\[' <source>`
277
+
278
+ ## settings
279
+
280
+ - **Storage**: `appSettings` and `connectionStrings` entries in
281
+ `web.config`.
282
+ Probe: `rg -n -g '*.config' '<add (key|name)=' <source>`
283
+ - **Read sites**: `ConfigurationManager.AppSettings[...]` and
284
+ `ConfigurationManager.ConnectionStrings[...]` reads in code, which can name
285
+ a key `web.config` never declares (an environment-variable override, a key
286
+ added only at deploy time).
287
+ Probe: `rg -n -g '*.cs' 'ConfigurationManager\.(AppSettings|ConnectionStrings)' <source>`
@@ -0,0 +1,280 @@
1
+ # Run operations
2
+
3
+ This file holds what is true across every phase of a run rather than any one
4
+ of them: how the orchestrator dispatches subagents, how their work gets
5
+ checkpointed into the store, what happens when a subagent goes quiet, and
6
+ what an agent sees when it contends with another for the store lock.
7
+ `SKILL.md`'s Aborting section names the batch-checkpoint discipline and says
8
+ Aborting assumes it was followed; `references/phases/enumerate.md` and
9
+ `references/phases/extract.md` both point here for dispatch and checkpoint
10
+ mechanics instead of restating them. This file is what they are pointing at.
11
+
12
+ This is not a phase manual. The six-section shape every file under
13
+ `references/phases/` uses (Purpose, Inputs, Procedure, What closes it,
14
+ Degradation, Commands) does not fit here: there is no single phase this file
15
+ belongs to, and nothing below is an exit condition `migrate check` gates.
16
+
17
+ A note on how to read what follows. Dispatch and the watchdog are
18
+ orchestration policy carried from a real migration campaign; nothing in the
19
+ CLI enforces a pulse interval or a discovery order, so those sections are
20
+ marked as guidance. The batch checkpoint loop and the store lock are
21
+ different: every claim about their mechanics, message text, and exit codes
22
+ below was run against a scratch store while writing this file, not recalled
23
+ or paraphrased.
24
+
25
+ ## Dispatch
26
+
27
+ Guidance, not CLI-enforced.
28
+
29
+ The orchestrator does source discovery itself, inline, before dispatching
30
+ anything. A subagent it dispatches starts at classification, not discovery,
31
+ with verified paths already in the prompt. A subagent that has to rediscover
32
+ its own paths is a subagent re-deriving something the orchestrator already
33
+ resolved, on its own dime, and re-litigating a decision that has already been
34
+ made once.
35
+
36
+ One lens per agent when lenses are heavy. Fanout unit is (surface, lens) in
37
+ phase 1 (enumerate) and capability in phase 3 (extract): an agent enumerating
38
+ `routes` under the `code` lens never needs to know what the `nav` lens found
39
+ for the same surface, and an agent mining `user-management` never needs to
40
+ know what `invoicing` looks like.
41
+
42
+ ## The batch checkpoint loop
43
+
44
+ Enforced. Every claim in this section was run.
45
+
46
+ Agents never write JSONL directly. The loop is:
47
+
48
+ 1. **Accumulate roughly ten rows** of findings in memory (a lens's elements,
49
+ a capability's requirements, whatever the current phase produces).
50
+ 2. **Write them to a batch file** (`batch.json`, or any name you choose) in
51
+ the envelope shape `references/phases/*.md` and `docs/reference.md`
52
+ describe: `{"batch": "...", "phase": "...", "rows": [...]}`.
53
+ 3. **Run `migrate import`** (`elements`, `reqs`, or `deltas`, matching what
54
+ you accumulated).
55
+ 4. **Commit.** A git commit right after the import succeeds is the
56
+ checkpoint. If the run is aborted or crashes anywhere after this point,
57
+ `.migrate/` at the last commit is a complete, valid store as of that
58
+ batch; nothing between commits needs to be recreated by memory, because
59
+ nothing important was ever only in memory.
60
+
61
+ **Validation happens at write time, so a malformed row cannot enter the
62
+ store.** Import is all-or-nothing: one invalid row in a batch means nothing
63
+ in that batch is written, not a partial batch you'd have to reason about on
64
+ resume. Run against a scratch store, a batch with one row missing its
65
+ `element` text produced:
66
+
67
+ ```
68
+ import: element route-get-api-orders-detail: missing element text
69
+ import: 1 error(s), nothing written
70
+ ```
71
+
72
+ Exit `1`. The element count in `elements.jsonl` was unchanged; the one good
73
+ row in that same batch was not written either, because the batch is the unit.
74
+
75
+ **The batch id lands in `phases.json`, which is what makes the checkpoint
76
+ mechanical rather than remembered.** A successful import appends the batch's
77
+ id and row count to that phase's `batches` array. `savePhases` writes the file
78
+ with `JSON.stringify(file, null, 2)`, so every phase nests under a top-level
79
+ `"phases"` key beside a `"version"`, and each batch object spreads across four
80
+ lines rather than sitting on one. Real content of `.migrate/phases.json` after
81
+ two imports into `enumerate`, copied out of the file unedited, indentation
82
+ included:
83
+
84
+ ```json
85
+ {
86
+ "version": 1,
87
+ "phases": {
88
+ "probe": {
89
+ "status": "pending",
90
+ "batches": [],
91
+ "pending": []
92
+ },
93
+ "enumerate": {
94
+ "status": "running",
95
+ "batches": [
96
+ {
97
+ "id": "b-routes-code-001",
98
+ "count": 1
99
+ },
100
+ {
101
+ "id": "b-routes-code-002",
102
+ "count": 1
103
+ }
104
+ ],
105
+ "pending": []
106
+ },
107
+ ```
108
+
109
+ (the remaining six phases follow in the same shape, each still `pending` with
110
+ an empty `batches`, and are cut here for length; nothing about them differs
111
+ from `probe` above.)
112
+
113
+ Nobody has to remember which batches already landed: `migrate status` and
114
+ `migrate phase enumerate` both read this array back, which is what makes
115
+ resuming a crashed run a read rather than a recollection.
116
+
117
+ ## When the store is locked
118
+
119
+ Enforced. Every claim in this section was run.
120
+
121
+ `import`, `census`, `phase --status`, and `reset` each take one lock over the
122
+ whole store (`.migrate/.lock`) for the length of their read-modify-write. Those
123
+ four are the whole set; every other command either only reads the store, or
124
+ does not touch it. Without
125
+ it, two agents importing at once each read the same base file, and whichever
126
+ one rewrites last silently discards the other's rows. The default wait is 30
127
+ seconds, polling with backoff (25ms, growing by roughly 1.5x each attempt, up
128
+ to a 250ms cap; read directly from `scripts/lock.ts`, not independently timed
129
+ here). A wait longer than that, or a lock file that looks broken rather than
130
+ merely held, ends in exit `3`.
131
+
132
+ **Exit `3` means retry, not fix the batch.** It is its own code, distinct
133
+ from `1` (a content or domain failure: your batch or query was well-formed
134
+ and the answer is no) and `2` (a malformed request: a bad flag, a missing
135
+ file). A lock failure says the request itself was fine; there is nothing
136
+ about your `batch.json`, or about the `--phase` you named, to go back and
137
+ inspect, regardless of which of the three ways below the lock failed. (Three
138
+ ways the lock can fail, not three commands that take it: four commands do,
139
+ listed above.)
140
+
141
+ That said, "retry" resolves the three underlying causes differently, and only
142
+ one of them clears on its own. Verified against a scratch store: a lock held
143
+ by a genuinely live process that outlasted the 30-second timeout produced the
144
+ timeout message and exit `3` on the first attempt (elapsed 30.091s), then, on
145
+ a second attempt with nothing else changed, waited out the remaining hold and
146
+ succeeded on its own once that process actually released the lock (elapsed
147
+ 4.627s, exit `0`, no `--force-unlock` involved). An ordinary contended or
148
+ slow lock genuinely does resolve with a bare retry, possibly more than one.
149
+ The two stale conditions below do not: retried bare, with no other change, a
150
+ corrupt lock file produced the identical exit `3` message on a second
151
+ attempt, immediately, because nothing about the file is different the second
152
+ time. Retry is the right first move for exit `3` in general; only
153
+ `--force-unlock`, after confirming safety, moves a stale lock forward.
154
+
155
+ **What a waiting agent sees.** `import`, `census`, and `reset` announce the
156
+ wait on stderr, once, the first time they see a live holder. Verified against a
157
+ scratch store, holding the lock with a real running process and then
158
+ attempting an import produced (path and pid shortened for readability; the
159
+ message text is exact):
160
+
161
+ ```
162
+ import: waiting for store lock (held by pid 51234 since 2026-08-07T09:14:02.001Z)
163
+ ```
164
+
165
+ `phase --status` is the one that does not print this line. It waits on the
166
+ same lock with the same backoff, silently: run against a scratch store with a
167
+ live holder released two seconds in, `migrate phase enumerate --status running`
168
+ printed nothing at all until it succeeded, 1.85 seconds later. `reset` does
169
+ print it, verified the same way against a live holder released two seconds in:
170
+
171
+ ```
172
+ reset: waiting for store lock (held by pid 74987 since 2026-08-08T00:00:00.000Z)
173
+ ```
174
+
175
+ If the wait for any of the four ends in a timeout instead, all four report it
176
+ the same way, with their own command name prefixed:
177
+
178
+ ```
179
+ import: timed out after 30000ms waiting for the store lock held by pid 51234 since 2026-08-07T09:14:02.001Z
180
+ ```
181
+
182
+ Verified with a real 30-second wait against a live holder (elapsed time
183
+ measured at 30.070s): the timeout is real, not advisory, and the message
184
+ names the same holder the waiting announcement did. When the lock file
185
+ itself is absent for the whole timeout instead of naming a holder, the
186
+ message falls back to naming the path instead:
187
+
188
+ ```
189
+ import: timed out after 30000ms waiting for the store lock at /repo/.migrate/.lock
190
+ ```
191
+
192
+ **Two conditions are refused immediately rather than waited out**, because
193
+ neither one is a lock that contention will ever resolve on its own: a holder
194
+ pid that has confirmed exited, and a lock file that fails to parse five
195
+ reads in a row. Verified against a scratch store, both returned in well
196
+ under a second, not anywhere near the 30-second timeout (0.031s and 0.259s
197
+ respectively in the runs that produced these):
198
+
199
+ ```
200
+ import: store lock held by pid 51234, which is not running. Re-run with --force-unlock after confirming no other agent is writing
201
+ ```
202
+
203
+ ```
204
+ import: store lock at /repo/.migrate/.lock is unreadable; re-run with --force-unlock after confirming no other agent is writing
205
+ ```
206
+
207
+ Both messages, and the timeout messages above, are copied verbatim from
208
+ `scripts/lock.ts`; only the pid, timestamp, and path are illustrative. Note
209
+ the two stale messages are not identically punctuated in the source (one
210
+ capitalizes "Re-run" after a period, the other lowercases "re-run" after a
211
+ semicolon) and this file preserves that rather than smoothing it over, so
212
+ what you read here matches what you'll see on screen exactly.
213
+
214
+ **`--force-unlock` is only appropriate after confirming the named pid is not
215
+ running and no other agent is mid-write.** It exists on `import`, `census`,
216
+ `phase`, and `reset`, the same four commands that take the lock at all.
217
+ Verified against a scratch store holding a dead holder's lock: each of the four
218
+ exits `3` without the flag and `0` with it. `reset` matters most here of the
219
+ four, since it is the one whose whole job is deleting rows. What it actually
220
+ does is blunt: it unlinks the lock file
221
+ unconditionally, before this process even checks who, if anyone, holds it.
222
+ The CLI does not verify staleness for you when you pass this flag; the
223
+ message says "after confirming no other agent is writing" because that
224
+ confirmation is entirely on you.
225
+
226
+ **Forcing a lock a live agent holds will lose that agent's rows.** If the
227
+ current holder is genuinely mid read-modify-write, unlinking its lock file
228
+ does not stop it. Both processes now race to rewrite the same store file, and
229
+ whichever one finishes and renames last wins, silently discarding whatever
230
+ the other one wrote. This is exactly the hazard the lock exists to prevent in
231
+ the first place, just re-opened by force. Confirm the named pid is actually
232
+ gone (not merely busy) before reaching for this flag, not after.
233
+
234
+ Verified: forcing a lock whose holder pid had genuinely exited (`pid 999999`,
235
+ not present on the system) removed the lock file and let the import through
236
+ in the same call:
237
+
238
+ ```
239
+ import elements: 1 added, 0 updated, batch b-routes-code-002
240
+ ```
241
+
242
+ ## Watchdog
243
+
244
+ Guidance, not CLI-enforced.
245
+
246
+ Pulse roughly every 30 minutes on a dispatched subagent that has been running
247
+ a while. Movement in its transcript (a tool call, a written batch, anything)
248
+ means it is alive; let it keep going. No movement gets exactly one resume,
249
+ and that resume says write-first: whatever findings it is holding, checkpoint
250
+ them (batch, import, commit) before doing anything else. Still nothing after
251
+ that resume means abandon the subagent and do the remaining work inline,
252
+ never send a second resume. A transcript that has already gone quiet once and
253
+ been resumed once is a transcript that keeps growing every time you poke it,
254
+ and a resumed transcript eventually becomes unresumable from bloat alone, so
255
+ the second attempt is not a smaller version of the first risk, it is a worse
256
+ one.
257
+
258
+ ## Report verification
259
+
260
+ Guidance for prose reports; the numeric part is now enforced by `migrate
261
+ check` instead.
262
+
263
+ The rule that every number in a report must be re-derived by a command the
264
+ writer actually ran exists because, before there was a store, coverage
265
+ arithmetic was hand-written and hand-verified, and the hand-written numbers
266
+ were wrong more than once. With the census living in the store, `migrate
267
+ check` (and `migrate status`) is the derivation: the coverage line, the
268
+ census balance, the run-state gate are commands, not claims someone typed.
269
+ The re-derive rule now narrows to prose: a queue item's evidence section, a
270
+ seam validator's raw script output, anything written in English about what a
271
+ lens or a validator found. Those still need to be something a command
272
+ actually produced or a person actually observed, quoted rather than
273
+ summarized from memory, for exactly the reason the original rule existed.
274
+
275
+ **Scorer and mapper stay separated for benchmark runs.** When the method
276
+ itself is being validated against a benchmark rather than run against a live
277
+ migration, the agent producing the mapping and the agent scoring it against
278
+ ground truth must not be the same agent, and must not share context that
279
+ would let one see the other's answer. This is a v1 documented discipline,
280
+ not a CLI verb; `migrate` has no benchmark command in this milestone.