@intentic/sandbox-contract 1.223.0 → 1.224.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 (86) hide show
  1. package/dist/chores/chores.js +1 -1
  2. package/dist/chores/chores.js.map +1 -1
  3. package/dist/chores/prompt.d.ts.map +1 -1
  4. package/dist/chores/prompt.js +3 -3
  5. package/dist/chores/prompt.js.map +1 -1
  6. package/dist/contracts/ports.contract.d.ts +11 -0
  7. package/dist/contracts/ports.contract.d.ts.map +1 -1
  8. package/dist/index.d.ts +11 -0
  9. package/dist/index.d.ts.map +1 -1
  10. package/dist/schemas.d.ts +22 -0
  11. package/dist/schemas.d.ts.map +1 -1
  12. package/dist/schemas.js +3 -0
  13. package/dist/schemas.js.map +1 -1
  14. package/package.json +4 -4
  15. package/src/agent-catalog.ts +78 -78
  16. package/src/agent-run-model.ts +8 -8
  17. package/src/capability-env.ts +3 -3
  18. package/src/capability-secrets.ts +5 -5
  19. package/src/chores/chores.ts +67 -67
  20. package/src/chores/digest.ts +3 -3
  21. package/src/chores/extension-update.ts +3 -3
  22. package/src/chores/fix-deps.ts +3 -3
  23. package/src/chores/probes.ts +16 -16
  24. package/src/chores/prompt.ts +9 -9
  25. package/src/chores/stack.ts +17 -17
  26. package/src/chores/verdict.ts +23 -23
  27. package/src/contract-lock.ts +7 -7
  28. package/src/contracts/activity.contract.ts +1 -1
  29. package/src/contracts/agent.contract.ts +2 -2
  30. package/src/contracts/agents.contract.ts +11 -11
  31. package/src/contracts/automations.contract.ts +5 -5
  32. package/src/contracts/capabilities.contract.ts +7 -7
  33. package/src/contracts/chores.contract.ts +3 -3
  34. package/src/contracts/ci.contract.ts +2 -2
  35. package/src/contracts/claude.contract.ts +3 -3
  36. package/src/contracts/drafts.contract.ts +1 -1
  37. package/src/contracts/endpoints.contract.ts +2 -2
  38. package/src/contracts/extensions.contract.ts +8 -8
  39. package/src/contracts/git.contract.ts +6 -6
  40. package/src/contracts/grok.contract.ts +4 -4
  41. package/src/contracts/host.contract.ts +5 -5
  42. package/src/contracts/intentic.contract.ts +2 -2
  43. package/src/contracts/logs.contract.ts +1 -1
  44. package/src/contracts/loops.contract.ts +8 -8
  45. package/src/contracts/personas.contract.ts +8 -8
  46. package/src/contracts/ports.contract.ts +1 -1
  47. package/src/contracts/prepush.contract.ts +2 -2
  48. package/src/contracts/providers.contract.ts +4 -4
  49. package/src/contracts/public.contract.ts +1 -1
  50. package/src/contracts/push.contract.ts +2 -2
  51. package/src/contracts/secrets.contract.ts +2 -2
  52. package/src/contracts/sessions.contract.ts +2 -2
  53. package/src/contracts/settings.contract.ts +3 -3
  54. package/src/contracts/share.contract.ts +1 -1
  55. package/src/contracts/skills.contract.ts +2 -2
  56. package/src/contracts/system.contract.ts +9 -9
  57. package/src/contracts/usage.contract.ts +1 -1
  58. package/src/contracts/vpn.contract.ts +5 -5
  59. package/src/contracts/workflows.contract.ts +12 -12
  60. package/src/contracts/workspace.contract.ts +13 -13
  61. package/src/conversation-ids.ts +7 -7
  62. package/src/events.ts +136 -136
  63. package/src/history-state.ts +12 -12
  64. package/src/host-protocol.ts +7 -7
  65. package/src/hostnames.ts +15 -15
  66. package/src/index.ts +8 -8
  67. package/src/listener-protocol.ts +12 -12
  68. package/src/model-order.ts +30 -30
  69. package/src/output-fields.ts +9 -9
  70. package/src/path-refs.ts +6 -6
  71. package/src/publish-drafts.ts +6 -6
  72. package/src/quick-model.ts +15 -15
  73. package/src/routes.ts +19 -19
  74. package/src/runtime-state.ts +22 -22
  75. package/src/schemas.ts +930 -918
  76. package/src/search-globs.ts +6 -6
  77. package/src/session-names.ts +5 -5
  78. package/src/share-paths.ts +7 -7
  79. package/src/sse.ts +1 -1
  80. package/src/state-portability.ts +8 -8
  81. package/src/terminal-protocol.ts +3 -3
  82. package/src/title.ts +20 -20
  83. package/src/tunnel-ids.ts +7 -7
  84. package/src/versions.ts +5 -5
  85. package/src/workflow-faults.ts +11 -11
  86. package/src/workspace-state.ts +119 -119
@@ -3,30 +3,30 @@ import { bucketOf, digestOf } from "./digest.js";
3
3
  import { CHORE_INVARIANTS, composeAsk, REPORT_INVARIANTS, TRIAGE_NOTE } from "./prompt.js";
4
4
  import { componentStem, frameworksOf, idiomRule, normalizePath, UI_FRAMEWORKS, usesTailwind } from "./stack.js";
5
5
 
6
- /* THE CHORE BOOK what routine maintenance a repository is owed, and what has to be TRUE before we say so.
6
+ /* THE CHORE BOOK, what routine maintenance a repository is owed, and what has to be TRUE before we say so.
7
7
  *
8
8
  * Everything in here is a standing offer: work that is worth doing eventually, that nobody will ever put on a
9
9
  * sprint board, and that a person cannot notice is overdue by looking at their editor. The engineering problem is
10
- * not finding such work any linter will hand you a thousand findings it is deciding which of them is worth
10
+ * not finding such work, any linter will hand you a thousand findings, it is deciding which of them is worth
11
11
  * interrupting somebody about, on a surface they will still be reading in six months.
12
12
  *
13
13
  * Three rules, and every entry below obeys all three:
14
14
  *
15
15
  * 1. DELTAS, NOT ABSOLUTES. "38 packages are undocumented" is a statistic; it will be true every day for a year,
16
16
  * and a tile lit every day teaches the eye to stop seeing the rail. "A package appeared that nothing explains"
17
- * is an event. So a chore's `digest` is built from the IDENTITIES of what it found which packages, which
18
- * advisories, which files and the rail speaks when that set changes, not while it is merely non-empty. The
17
+ * is an event. So a chore's `digest` is built from the IDENTITIES of what it found, which packages, which
18
+ * advisories, which files, and the rail speaks when that set changes, not while it is merely non-empty. The
19
19
  * standing count still shows inside the panel, next to the thing it describes, which is where a statistic
20
20
  * belongs.
21
21
  *
22
22
  * 2. LEADER-RELATIVE, NOT TUNED. Nowhere in here is there a threshold that would need a different value for a
23
- * Rust repo, a fresh scaffold, or a ten-year monolith with one deliberate exception (duplication's 5%, which
23
+ * Rust repo, a fresh scaffold, or a ten-year monolith, with one deliberate exception (duplication's 5%, which
24
24
  * is a percentage of the tree and therefore already scale-free). "Three times the median of its own ranking"
25
25
  * needs no calibration and cannot rot.
26
26
  *
27
27
  * 3. THE EVIDENCE IS THE TRUTH; THE LEDGER ONLY DEBOUNCES. Nothing here can be ticked off. A chore goes quiet
28
- * because the measurement moved, which means someone fixing it by hand or an unrelated change fixing it by
29
- * accident is registered exactly like a chore turn doing it. The ledger's only power is to stop the rail
28
+ * because the measurement moved, which means someone fixing it by hand, or an unrelated change fixing it by
29
+ * accident, is registered exactly like a chore turn doing it. The ledger's only power is to stop the rail
30
30
  * repeating itself about evidence a turn has already been spent on (verdict.ts).
31
31
  *
32
32
  * What is NOT here is as deliberate. There is no composite score, no letter grade, no "health: 78%". Those are
@@ -38,7 +38,7 @@ import { componentStem, frameworksOf, idiomRule, normalizePath, UI_FRAMEWORKS, u
38
38
  export type ChoreStance = "act" | "report";
39
39
 
40
40
  /* WHAT KIND OF CLAIM A CHORE MAKES ON SOMEONE'S ATTENTION. Four of them, ordered from "this is a risk you are
41
- * carrying right now" to "this is worth thinking about this quarter" see CHORE_KINDS at the foot of this file,
41
+ * carrying right now" to "this is worth thinking about this quarter", see CHORE_KINDS at the foot of this file,
42
42
  * which carries the argument and the words the panel groups under. */
43
43
  export type ChoreKind = "carrying" | "accruing" | "drifting" | "surveying";
44
44
 
@@ -56,15 +56,15 @@ export interface ChoreContext {
56
56
  export interface ChoreFinding {
57
57
  // One line, in numbers, for the row. The reader decides from this whether to open anything.
58
58
  readonly headline: string;
59
- // The evidence itself, one claim per line what the panel lists under the row, and what makes the headline
59
+ // The evidence itself, one claim per line, what the panel lists under the row, and what makes the headline
60
60
  // checkable rather than something to be believed.
61
61
  readonly detail: readonly string[];
62
62
  // The identity of THIS evidence. See digest.ts: it is what the rail's transitions are measured against.
63
63
  readonly digest: string;
64
- // `warning` is for a risk the owner is carrying right now a live advisory, a runtime past its EOL. Everything
64
+ // `warning` is for a risk the owner is carrying right now, a live advisory, a runtime past its EOL. Everything
65
65
  // else is `info`, including large and ugly numbers, because "there is a lot of it" is not an emergency.
66
66
  readonly severity: "info" | "warning";
67
- // The numbers again, in the agent's terms, for the prompt's "Why:" line. Exact the agent may recount them.
67
+ // The numbers again, in the agent's terms, for the prompt's "Why:" line. Exact, the agent may recount them.
68
68
  readonly why: string;
69
69
  }
70
70
 
@@ -81,7 +81,7 @@ export interface Chore {
81
81
  * reason: the reading order is the one editorial claim this surface makes, and a claim spelled as a comment
82
82
  * beside a hand-maintained list is one nobody can check and the compiler cannot keep. */
83
83
  readonly kind: ChoreKind;
84
- /* THE RULE, in words what has to be true for this chore to be due, stated so a reader can check it against
84
+ /* THE RULE, in words, what has to be true for this chore to be due, stated so a reader can check it against
85
85
  * the evidence below it and disagree.
86
86
  *
87
87
  * This is not decoration. A row that says "4 majors waiting" and nothing else is asking to be taken on
@@ -89,23 +89,23 @@ export interface Chore {
89
89
  * argue with, and arguing with it is how the book gets better. It rides into the prompt too, so the agent is
90
90
  * told the rule it was woken by rather than left to infer it from the numbers.
91
91
  *
92
- * Kept as prose next to the code that implements it, which means it can drift from it the tests below
92
+ * Kept as prose next to the code that implements it, which means it can drift from it, the tests below
93
93
  * cannot check English. The rule for writing one: say the THRESHOLD, not the subject. "Duplication is high"
94
94
  * is a topic; "more than 5% of the tree is duplicated" is a criterion. */
95
95
  readonly criterion: string;
96
- /* WHETHER THIS IS A QUESTION WORTH ASKING OF THIS REPOSITORY AT ALL returns undefined when it is, and what
96
+ /* WHETHER THIS IS A QUESTION WORTH ASKING OF THIS REPOSITORY AT ALL, returns undefined when it is, and what
97
97
  * is MISSING when it is not.
98
98
  *
99
99
  * Distinct from `assess`, and the distinction is the whole point: `assess` asks whether the answer is yes,
100
100
  * this asks whether the question makes sense. "Re-read the documentation against the code" in a repository
101
- * with no documentation is not a chore that is currently clear it is one that will never apply here, and
101
+ * with no documentation is not a chore that is currently clear, it is one that will never apply here, and
102
102
  * showing it as clear says we checked something we cannot check. A chore that does not apply is dropped from
103
103
  * the panel entirely; a line in the scope strip records that it was considered.
104
104
  *
105
- * A BARE CAUSE "no Dockerfile", never "this repository ships no Dockerfile, so there is no image to slim".
105
+ * A BARE CAUSE, "no Dockerfile", never "this repository ships no Dockerfile, so there is no image to slim".
106
106
  * Same spelling as `ProbeSpec.unavailable`, and for the same reason both surfaces need: one absent
107
107
  * package.json rules out five chores, and five sentences saying so at length is the wall of text this phrasing
108
- * exists to prevent. The panel groups by this string, so the CONSEQUENCE which chores it costs is the list
108
+ * exists to prevent. The panel groups by this string, so the CONSEQUENCE, which chores it costs, is the list
109
109
  * beside it rather than a clause repeated inside every entry. Identical causes must be spelled identically or
110
110
  * they group apart.
111
111
  *
@@ -113,7 +113,7 @@ export interface Chore {
113
113
  * fact the daemon holds without measuring anything. If a gate needed a probe it would be describing the
114
114
  * answer rather than the question. */
115
115
  readonly applies?: (signals: ChoreSignals) => string | undefined;
116
- // Whether the turn is allowed to CHANGE anything. Not a hint it selects the invariants block, and a
116
+ // Whether the turn is allowed to CHANGE anything. Not a hint, it selects the invariants block, and a
117
117
  // report-stance chore is told in words that editing would be a surprise.
118
118
  readonly stance: ChoreStance;
119
119
  // Probes that must have run and succeeded before this chore can be assessed at all. Missing ⇒ `unavailable`:
@@ -126,13 +126,13 @@ export interface Chore {
126
126
  // A survey has no measurement: it is due on its cadence and clear otherwise. Named rather than inferred from
127
127
  // an empty `needs`, because the two are different claims and the panel says which one a row is.
128
128
  readonly survey?: true;
129
- /* THE SCHEDULED FORM, for the chores worth running unattended what the Automations page offers as a
129
+ /* THE SCHEDULED FORM, for the chores worth running unattended, what the Automations page offers as a
130
130
  * one-click "code chore", and the second way this book is consumed.
131
131
  *
132
132
  * The two modes are genuinely different and both are wanted. The Maintenance panel is EVIDENCE-driven: it
133
133
  * reads what the daemon already measured and offers a turn against a specific finding you can read first. An
134
134
  * automation is SCHEDULE-driven: it wakes on a clock, at 3am, with nobody watching. So an automation cannot
135
- * carry a finding there is no verdict at fire time and instead it carries a GUARD: a shell one-liner that
135
+ * carry a finding, there is no verdict at fire time, and instead it carries a GUARD: a shell one-liner that
136
136
  * runs for free on the sandbox's own clock and exits non-zero to skip, so the half that costs a turn only
137
137
  * starts when there is something to start it for.
138
138
  *
@@ -144,12 +144,12 @@ export interface Chore {
144
144
  readonly guard: string;
145
145
  readonly note: string;
146
146
  readonly report: string;
147
- // How the woken turn is told what it is looking at the "Why:" line, in place of a finding.
147
+ // How the woken turn is told what it is looking at, the "Why:" line, in place of a finding.
148
148
  readonly woke: string;
149
149
  };
150
150
  readonly assess: (context: ChoreContext) => ChoreFinding | undefined;
151
151
  // The prompt's three variable parts (prompt.ts owns the shape). `diagnosis` says what the numbers MEAN, `goal`
152
- // says what shape to move towards never a design and `done` is falsifiable by the agent itself.
152
+ // says what shape to move towards, never a design, and `done` is falsifiable by the agent itself.
153
153
  readonly diagnosis: string;
154
154
  readonly goal: string;
155
155
  readonly done: string;
@@ -158,7 +158,7 @@ export interface Chore {
158
158
  const DAY_MS = 86_400_000;
159
159
 
160
160
  /* Where a scheduled chore's guard leaves its report for the woken turn to read. Under /tmp because they are
161
- * inputs to a turn that starts moments later, never something to keep and deliberately the SAME paths the
161
+ * inputs to a turn that starts moments later, never something to keep, and deliberately the SAME paths the
162
162
  * probe runner uses, so a workspace that runs both does not keep two copies of the same measurement. */
163
163
  const AUDIT_REPORT = `/tmp/intentic-chore-audit.json`;
164
164
  const KNIP_REPORT = `/tmp/intentic-chore-knip.json`;
@@ -167,7 +167,7 @@ const JSCPD_REPORT = `${JSCPD_DIR}/jscpd-report.json`;
167
167
 
168
168
  // How a repo is named to a person and to an agent. "root" is the wire id the daemon's git and health routes
169
169
  // already use for the workspace's own repository, and it is a word an agent would otherwise read as a directory
170
- // called "root" so it is spelled out here, once, rather than at every call site that builds a prompt.
170
+ // called "root", so it is spelled out here, once, rather than at every call site that builds a prompt.
171
171
  export const repoLabel = (repo: string): string => (repo === `root` || repo === `` ? `the workspace root repository` : repo);
172
172
 
173
173
  // The same repository, named for a surface that has a 16rem column or a chip to say it in. `repoLabel` is prose
@@ -181,7 +181,7 @@ const plural = (count: number, one: string, many = `${one}s`): string => `${coun
181
181
  // row is a morning's work or a project.
182
182
  const outdatedLine = (entry: OutdatedPackage): string => `${entry.kind} · ${entry.name} ${entry.current} → ${entry.latest}`;
183
183
 
184
- // The `facts` of a probe that actually ran. Anything else never run, unavailable, failed reads as absent, so
184
+ // The `facts` of a probe that actually ran. Anything else, never run, unavailable, failed, reads as absent, so
185
185
  // no assess() can accidentally treat an unmeasured repo as a measured clean one.
186
186
  const factsOf = <T extends ProbeId>(context: ChoreContext, id: T): Extract<NonNullable<ProbeResult["facts"]>, { id: T }> | undefined => {
187
187
  const probe = context.probes.get(id);
@@ -198,7 +198,7 @@ const BLOCKING = new Set<Advisory["severity"]>([`critical`, `high`]);
198
198
  /* SECURITY. The only chore with no cadence at all: an advisory is not something that becomes worth looking at
199
199
  * after thirty days, and there is nothing periodic about it. It is also the only one that reaches `warning`
200
200
  * routinely, which is exactly why the bar is critical-or-high and production-or-dev is carried through to the
201
- * prompt rather than flattened a moderate advisory in a build-time-only tool badging red is how `warning` stops
201
+ * prompt rather than flattened, a moderate advisory in a build-time-only tool badging red is how `warning` stops
202
202
  * meaning anything within a week. */
203
203
  const security: Chore = {
204
204
  id: `security-advisories`,
@@ -244,7 +244,7 @@ const security: Chore = {
244
244
  digest: digestOf(...blocking.map((advisory) => `${advisory.name}@${advisory.severity}`).toSorted()),
245
245
  severity: production.length > 0 ? `warning` : `info`,
246
246
  // Named, not counted. "1 high advisory" tells an agent nothing it can act on, and the first thing it
247
- // would have to do is re-derive the list we already have badly, because pnpm audit is slow and it
247
+ // would have to do is re-derive the list we already have, badly, because pnpm audit is slow and it
248
248
  // would be reading a different tree by then.
249
249
  why:
250
250
  `pnpm audit reports ${plural(blocking.length, `high or critical advisory`, `high or critical advisories`)} against ` +
@@ -262,7 +262,7 @@ const security: Chore = {
262
262
  };
263
263
 
264
264
  /* DEPENDENCIES. Majors are the finding; the total is context. A repo that is forty patch releases behind is a
265
- * morning's work and does not need a rail tile, while one major on a framework is a project so the digest is
265
+ * morning's work and does not need a rail tile, while one major on a framework is a project, so the digest is
266
266
  * built from WHICH packages have a major waiting, and a new one appearing is the event. The total count rides
267
267
  * along bucketed (digest.ts) so that ordinary drift, which is constant, does not read as news. */
268
268
  const OUTDATED_NOISE_FLOOR = 20;
@@ -297,7 +297,7 @@ const dependencies: Chore = {
297
297
  detail: majors.toSorted((left, right) => left.name.localeCompare(right.name)).map(outdatedLine),
298
298
  digest: digestOf(...majors.map((entry) => `${entry.name}@${entry.latest}`).toSorted(), `total:${bucketOf(facts.packages.length)}`),
299
299
  severity: `info`,
300
- // The majors are named because they are what the turn is actually about the minors and patches are a
300
+ // The majors are named because they are what the turn is actually about, the minors and patches are a
301
301
  // bulk operation the agent will enumerate itself, and listing four hundred of them here would bury it.
302
302
  why:
303
303
  `pnpm outdated reports ${plural(facts.packages.length, `dependency`, `dependencies`)} behind the registry in ` +
@@ -430,7 +430,7 @@ const duplication: Chore = {
430
430
  done: `Done when every clone in the report has either a named extraction or a one-line reason it should stay.`,
431
431
  };
432
432
 
433
- /* DOCUMENTATION. The evidence is a package with no README which IS its architecture document in this
433
+ /* DOCUMENTATION. The evidence is a package with no README, which IS its architecture document in this
434
434
  * workspace, so this is a stat on the package directory rather than a lookup in a parallel tree. It sounds like
435
435
  * a coverage statistic
436
436
  * and would be one if the rail read it directly. It does not: the digest is the SET of undocumented package
@@ -472,11 +472,11 @@ const documentation: Chore = {
472
472
  };
473
473
 
474
474
  /* COMPLEXITY. The one chore whose evidence comes from the resident index rather than a subprocess, and the one
475
- * most at risk of being a ranking laundered into a to-do list there is ALWAYS a top of a hotspot ranking, and
475
+ * most at risk of being a ranking laundered into a to-do list, there is ALWAYS a top of a hotspot ranking, and
476
476
  * "your worst file" is not a finding. So it does not report the ranking. It reports the two shapes within it that
477
477
  * are genuinely arguable:
478
478
  *
479
- * volatile AND load-bearing a hotspot that is also a key module: every edit ripples outward.
479
+ * volatile AND depended-on a hotspot that is also a key module: every edit ripples outward.
480
480
  * out of proportion branching three times the median of its own ranking: tangled, not merely busy.
481
481
  *
482
482
  * Both are relative to the same list the user is reading, so nothing here needs tuning per repository or per
@@ -539,7 +539,7 @@ const complexity: Chore = {
539
539
 
540
540
  /* RUNTIME. A static table, and it is honest about being one: there is no network call here, so the dates below
541
541
  * are a fact about the day this file was last edited rather than a live feed. That is the right trade for a
542
- * signal that moves twice a year and must work on a box with no outbound access but it does mean this table is
542
+ * signal that moves twice a year and must work on a box with no outbound access, but it does mean this table is
543
543
  * maintenance in its own right, and a major missing from it reads as "not end-of-life", which is the safe way to
544
544
  * be wrong. Source: nodejs/Release. */
545
545
  const NODE_EOL: Readonly<Record<number, string>> = {
@@ -550,7 +550,7 @@ const NODE_EOL: Readonly<Record<number, string>> = {
550
550
  24: `2028-04-30`,
551
551
  };
552
552
  // How far ahead of an end-of-life date the chore starts speaking. A quarter, because moving a runtime is planned
553
- // work telling someone the day security patches stop is telling them too late to do anything but scramble.
553
+ // work, telling someone the day security patches stop is telling them too late to do anything but scramble.
554
554
  const EOL_HORIZON_MS = 90 * DAY_MS;
555
555
 
556
556
  const runtime: Chore = {
@@ -607,7 +607,7 @@ const runtime: Chore = {
607
607
  * using a library for this?"). Two libraries that solve the same problem in one tree is a fact, not an opinion:
608
608
  * somebody added the second one without removing the first, both are now in the bundle, and new code picks
609
609
  * whichever the neighbouring file used. The table below is deliberately short and only names categories where
610
- * having two is genuinely a mistake not, say, two test runners, which is an ordinary migration. */
610
+ * having two is genuinely a mistake, not, say, two test runners, which is an ordinary migration. */
611
611
  const CATEGORIES: readonly { readonly category: string; readonly members: readonly string[] }[] = [
612
612
  { category: `date handling`, members: [`moment`, `dayjs`, `date-fns`, `luxon`, `js-joda`] },
613
613
  { category: `HTTP clients`, members: [`axios`, `got`, `node-fetch`, `superagent`, `undici`, `request`] },
@@ -649,7 +649,7 @@ const libraries: Chore = {
649
649
  },
650
650
  diagnosis: `Two libraries for one job means both ship, both need upgrading, and new code picks whichever the neighbouring file used.`,
651
651
  goal:
652
- `For each overlapping pair, find out which one is actually load-bearing how many call sites each has, whether one is a ` +
652
+ `For each overlapping pair, find out which one is actually used. How many call sites each has, whether one is a ` +
653
653
  `transitive dependency nobody chose, and whether either is unmaintained. Recommend the one to keep and estimate the migration ` +
654
654
  `honestly, including the call sites where the two libraries genuinely differ in behaviour. Where the overlap is deliberate or ` +
655
655
  `the second is only transitive, say so and close the question.`,
@@ -659,17 +659,17 @@ const libraries: Chore = {
659
659
  /* ---- THE FRONT-END CHORES -------------------------------------------------------------------------------------
660
660
  *
661
661
  * Four chores that only exist where a UI framework does, kept together because they share one gate and one
662
- * probe and split across the reading order in CHORES, since where a row belongs is decided by what KIND of
662
+ * probe, and split across the reading order in CHORES, since where a row belongs is decided by what KIND of
663
663
  * finding it is, not by which file paragraph it was written in.
664
664
  *
665
665
  * They gate on `shape.deps` rather than on `signals.packages`, and that is not interchangeable. `packages` is
666
- * populated from pnpm-workspace.yaml, so it is EMPTY for a repository that is not a monorepo which is what a
666
+ * populated from pnpm-workspace.yaml, so it is EMPTY for a repository that is not a monorepo, which is what a
667
667
  * Vite app, a Next app and an Angular CLI project all are. A framework gate reading it would be permanently dark
668
668
  * in the overwhelming majority of the repositories these four were written for, and dark silently: the chores
669
669
  * would not appear, the footer would say the repository has no packages, and nothing would look broken.
670
670
  *
671
671
  * All four also say something the rest of the book does not have to. A component, a class name and a bundle chunk
672
- * are things nobody sees the whole of you read one component at a time, and the tenth copy of a button looks
672
+ * are things nobody sees the whole of, you read one component at a time, and the tenth copy of a button looks
673
673
  * exactly like the first nine did. That is the same argument the whole surface rests on, just further from the
674
674
  * places a compiler will ever help. */
675
675
 
@@ -689,21 +689,21 @@ const bytesLabel = (bytes: number): string => (bytes >= 1024 * 1024 ? `${(bytes
689
689
  /* BUNDLE. What a browser downloads before anything appears, which is the fact about a front-end that is furthest
690
690
  * from anything visible in an editor: every dependency looks the same size in an import statement.
691
691
  *
692
- * The criterion is a SHARE, and that is deliberate it is the second exception to the book's leader-relative
692
+ * The criterion is a SHARE, and that is deliberate, it is the second exception to the book's leader-relative
693
693
  * rule, and it earns the same defence duplication's 5% does. A byte threshold would need a different value for a
694
694
  * marketing page and an IDE, would be argued about forever, and would be wrong the moment either one grew. "One
695
695
  * chunk is more than half of everything you ship" needs no calibration: it says the build is not split, which is
696
696
  * true or false at any size. A well-split app has its largest chunk well under this whatever it weighs, and a
697
- * small app that genuinely is one chunk trips it and is right to that IS its entire download.
697
+ * small app that genuinely is one chunk trips it and is right to, that IS its entire download.
698
698
  *
699
699
  * Report-stance. Where the split boundaries go is a routing and product decision, and an agent that lazily
700
700
  * imported things unattended at three in the morning would be making it. */
701
701
  const BUNDLE_SHARE_FLOOR = 50;
702
- // Below this there is no ranking to be an outlier in two files cannot tell you anything about how a build is
702
+ // Below this there is no ranking to be an outlier in, two files cannot tell you anything about how a build is
703
703
  // divided, and the largest of them is over half by arithmetic rather than by fault.
704
704
  const BUNDLE_MIN_ASSETS = 3;
705
705
 
706
- /* An asset's name with its content hash taken out `assets/vendor-DlAUqK2U.js` becomes `assets/vendor.js`.
706
+ /* An asset's name with its content hash taken out, `assets/vendor-DlAUqK2U.js` becomes `assets/vendor.js`.
707
707
  *
708
708
  * Without this the digest changes on every single build, because a content hash changing is the entire point of a
709
709
  * content hash. The chore would badge after every `pnpm build` while reporting nothing new, which is precisely
@@ -734,7 +734,7 @@ const bundleWeight: Chore = {
734
734
  return undefined;
735
735
  }
736
736
  // By GZIP, not by raw bytes. What is on disk is not what crosses the wire, and a large but highly
737
- // compressible asset a source map comment, a big JSON blob is not the download this is about.
737
+ // compressible asset, a source map comment, a big JSON blob, is not the download this is about.
738
738
  const ranked = assets.toSorted((left, right) => right.gzip - left.gzip);
739
739
  const largest = ranked[0];
740
740
  if (largest === undefined) {
@@ -780,7 +780,7 @@ const bundleWeight: Chore = {
780
780
  };
781
781
 
782
782
  /* FRAMEWORK IDIOMS. A migration nobody finished, which is the most ordinary state for a front-end of any age: the
783
- * new way arrived, the new files use it, and the old files keep working so nothing ever forces the rest.
783
+ * new way arrived, the new files use it, and the old files keep working, so nothing ever forces the rest.
784
784
  *
785
785
  * The digest is the one place this chore differs in shape from its neighbours, and it has to. Digesting the file
786
786
  * identities, the way the documentation chore does, would re-badge every time anyone touched any of two hundred
@@ -807,13 +807,13 @@ const frameworkIdiom: Chore = {
807
807
  * embarrassing.
808
808
  *
809
809
  * AN IDIOM THIS BUILD HAS NEVER HEARD OF. The daemon composes the sweep from its own copy of the table, so
810
- * a sandbox image ahead of the browser can report a rule that has no label or replacement here and a row
810
+ * a sandbox image ahead of the browser can report a rule that has no label or replacement here, and a row
811
811
  * saying "42 files use react-foo" with no idea what to do about them is worse than no row.
812
812
  *
813
813
  * AN IDIOM BELONGING TO A FRAMEWORK THIS REPOSITORY DOES NOT USE. A probe's command is a fixed string, so
814
814
  * every rule in the table is swept in every repository, and an Angular pattern gets its chance in a Vue
815
- * codebase: `RouterModule.forRoot` inside a comment, a `*ngIf` in an example string, and the case that
816
- * caught this the book's own rule table quoting its own patterns back at it. What the repository
815
+ * codebase: `RouterModule.forRoot` inside a comment, a `*ngIf` in an example string, and, the case that
816
+ * caught this, the book's own rule table quoting its own patterns back at it. What the repository
817
817
  * DECLARES is the arbiter, the same `deps` the gate above reads. */
818
818
  const frameworks = new Set(frameworksOf(context.signals.shape.deps).map((framework) => framework.id));
819
819
  const found = facts.scan.idioms.flatMap(({ id, files }) => {
@@ -855,13 +855,13 @@ const frameworkIdiom: Chore = {
855
855
  * code and neither knows the other exists.
856
856
  *
857
857
  * TWO KINDS OF EVIDENCE, and they catch opposite failures. A NAME FAMILY catches components that were written
858
- * separately and never shared a line `BaseButton.vue` and `ButtonV2.tsx` reduce to the same stem, and no clone
858
+ * separately and never shared a line, `BaseButton.vue` and `ButtonV2.tsx` reduce to the same stem, and no clone
859
859
  * detector will ever connect them. A CLONE PAIR catches the reverse: two components with unrelated names doing
860
860
  * the same work, which is what jscpd is actually good at, filtered to the pairs where both sides are components
861
861
  * so it is a finding about the UI rather than a slice of the repo-wide duplication chore.
862
862
  *
863
863
  * It needs jscpd rather than reading it if present. Half a measurement would let the row claim it had looked for
864
- * shared logic in a repository where that sweep has never run the exact "measured and found nothing" lie the
864
+ * shared logic in a repository where that sweep has never run, the exact "measured and found nothing" lie the
865
865
  * `unavailable` state exists to make impossible. jscpd is already running weekly for the duplication chore in any
866
866
  * Node repository, so the honest choice is also the free one. */
867
867
  const componentOverlap: Chore = {
@@ -945,7 +945,7 @@ const componentOverlap: Chore = {
945
945
 
946
946
  /* TAILWIND. A design system exists to make a decision once; an arbitrary value is that decision being made again,
947
947
  * inline, by whoever was in the file. What makes this measurable rather than a matter of taste is that Tailwind
948
- * spells the bypass out loud `bg-[#3b82f6]` is the palette being stepped around, in the markup, in a form no
948
+ * spells the bypass out loud, `bg-[#3b82f6]` is the palette being stepped around, in the markup, in a form no
949
949
  * reviewer can miss and no linter mentions.
950
950
  *
951
951
  * Deliberately NOT every arbitrary value. `grid-cols-[1fr_auto]` is the feature working as intended and there is
@@ -977,7 +977,7 @@ const tailwindBypass: Chore = {
977
977
  return {
978
978
  headline: `${plural(total, `hard-coded value`)} across ${plural(bypasses.length, `file`)}`,
979
979
  detail: worst.map((entry) => `${entry.path} · ${plural(entry.count, `value`)}`),
980
- // The worst files by identity a new file arriving at the top of this list is the event with the
980
+ // The worst files by identity, a new file arriving at the top of this list is the event, with the
981
981
  // spread and the total riding along bucketed, because both drift by one every time anyone writes
982
982
  // markup and neither is worth interrupting somebody about.
983
983
  digest: digestOf(...worst.map((entry) => entry.path).toSorted(), `files:${bucketOf(bypasses.length)}`, `total:${bucketOf(total)}`),
@@ -1002,7 +1002,7 @@ const tailwindBypass: Chore = {
1002
1002
  };
1003
1003
 
1004
1004
  /* THE SURVEYS. Chores with no measurement at all, and they are here because the absence of a measurement is not
1005
- * the absence of value these are the reviews a codebase silently rots without, and none of them can be detected
1005
+ * the absence of value, these are the reviews a codebase silently rots without, and none of them can be detected
1006
1006
  * by a tool. Their trigger is the calendar, and the ledger is what makes that trigger honest: a survey is due
1007
1007
  * because it has not been done in a quarter, which is a claim the panel can show and the reader can check.
1008
1008
  *
@@ -1011,8 +1011,8 @@ const tailwindBypass: Chore = {
1011
1011
  *
1012
1012
  * A SURVEY NEEDS ITS `applies` GATE MORE THAN A MEASURED CHORE DOES, not less, and this is the trap the shape of
1013
1013
  * the thing sets. A measured chore is gated by its own evidence for free: no undocumented packages, no finding,
1014
- * no row. A survey has no evidence to be absent "90 days have passed" is true of every repository in the
1015
- * world so without a gate it fires everywhere, forever, including in the repositories where its subject does
1014
+ * no row. A survey has no evidence to be absent, "90 days have passed" is true of every repository in the
1015
+ * world, so without a gate it fires everywhere, forever, including in the repositories where its subject does
1016
1016
  * not exist. "Re-read the documentation against the code" in a repository with no documentation is the exact
1017
1017
  * failure, and it is not a hypothetical: it is what this helper did before the gate existed.
1018
1018
  *
@@ -1028,7 +1028,7 @@ interface SurveySpec {
1028
1028
  readonly done: string;
1029
1029
  readonly cadenceDays: number;
1030
1030
  // What must exist in the repository for this review to have a subject. Required, not optional, precisely
1031
- // because forgetting it is the failure mode above a survey that genuinely applies everywhere still has to
1031
+ // because forgetting it is the failure mode above, a survey that genuinely applies everywhere still has to
1032
1032
  // say so out loud, with `() => undefined`.
1033
1033
  readonly applies: (signals: ChoreSignals) => string | undefined;
1034
1034
  }
@@ -1104,11 +1104,11 @@ const deprecated = survey({
1104
1104
  /* THE CHORE THAT NAMED THE PROBLEM. Gated on documents actually EXISTING, which is the whole reason `applies`
1105
1105
  * exists: without it this survey fires on its cadence in every repository, including the ones with nothing to
1106
1106
  * re-read, and the first thing an owner of a fresh workspace sees is an offer to re-read documentation they have
1107
- * never written. That is not a chore being wrong about a threshold it is the surface admitting it never looked.
1107
+ * never written. That is not a chore being wrong about a threshold, it is the surface admitting it never looked.
1108
1108
  *
1109
1109
  * Note which fact it gates on: the MAP, not the directory. An empty `docs/architecture/` is a directory somebody
1110
1110
  * made and never filled, and a gate on the directory would put the chore back exactly where it started. The
1111
- * survey then reads the package READMEs too they are the package pages but a repo with no map has not been
1111
+ * survey then reads the package READMEs too, they are the package pages, but a repo with no map has not been
1112
1112
  * documented at all, and that is the case worth staying quiet for. */
1113
1113
  const documentationDrift = survey({
1114
1114
  id: `documentation-drift`,
@@ -1125,9 +1125,9 @@ const documentationDrift = survey({
1125
1125
  applies: (signals) => (signals.shape.docs.length > 0 ? undefined : `no architecture documents`),
1126
1126
  });
1127
1127
 
1128
- /* THE TWO CHORES THAT ONLY EXIST WHERE THEIR SUBJECT DOES. Both are surveys nothing here can measure whether a
1128
+ /* THE TWO CHORES THAT ONLY EXIST WHERE THEIR SUBJECT DOES. Both are surveys, nothing here can measure whether a
1129
1129
  * pipeline caches well or an image is bigger than it needs to be without running them, and running someone's CI
1130
- * to find out would be a strange thing for a maintenance panel to do so both are gated on the artefact itself.
1130
+ * to find out would be a strange thing for a maintenance panel to do, so both are gated on the artefact itself.
1131
1131
  * Together they are the argument for `applies` being first-class rather than folded into `assess`: neither has
1132
1132
  * any evidence to be absent, and in a repository with no pipeline and no image both would otherwise sit in the
1133
1133
  * list forever, permanently due, describing work that cannot be done. */
@@ -1167,11 +1167,11 @@ const images = survey({
1167
1167
  * order these were written in. It narrows from "this is a risk you are carrying right now" to "this is worth
1168
1168
  * thinking about this quarter".
1169
1169
  *
1170
- * This used to be a comment above a hand-sorted array the four kinds named in prose, the order maintained by
1170
+ * This used to be a comment above a hand-sorted array, the four kinds named in prose, the order maintained by
1171
1171
  * whoever added the last chore, and nothing anywhere that could check the two agreed. It was also thrown away at
1172
1172
  * render: the panel listed every chore in one flat column, so the single editorial claim this surface makes
1173
1173
  * ("a live advisory and a quarterly re-read are not the same kind of thing") was invisible and therefore
1174
- * unarguable on a page whose whole design is that every claim shows its working.
1174
+ * unarguable, on a page whose whole design is that every claim shows its working.
1175
1175
  *
1176
1176
  * So the kinds are data. They order the book here, they group the rows in the panel, and `caption` is the
1177
1177
  * sentence the panel puts beside each group so the grouping argues for itself.
@@ -1186,7 +1186,7 @@ export interface ChoreKindSpec {
1186
1186
  readonly kind: ChoreKind;
1187
1187
  // Title case, because the panel renders it as a group heading rather than as a sentence.
1188
1188
  readonly label: string;
1189
- // Why these belong together, in the reader's terms what the group is CLAIMING about the rows under it.
1189
+ // Why these belong together, in the reader's terms, what the group is CLAIMING about the rows under it.
1190
1190
  readonly caption: string;
1191
1191
  }
1192
1192
 
@@ -1197,7 +1197,7 @@ export const CHORE_KINDS: readonly ChoreKindSpec[] = [
1197
1197
  { kind: `surveying`, label: `Surveying`, caption: `periodic reads with nothing measuring them — due because it has been that long` },
1198
1198
  ];
1199
1199
 
1200
- // Declaration order, which decides nothing but the order WITHIN a kind the sort below is stable, so the two
1200
+ // Declaration order, which decides nothing but the order WITHIN a kind, the sort below is stable, so the two
1201
1201
  // facts stay separable: this list is where a chore is written down, CHORE_KINDS is where it is ranked.
1202
1202
  const BOOK: readonly Chore[] = [
1203
1203
  security,
@@ -1222,7 +1222,7 @@ const BOOK: readonly Chore[] = [
1222
1222
  const KIND_ORDER: readonly ChoreKind[] = CHORE_KINDS.map(({ kind }) => kind);
1223
1223
 
1224
1224
  // Sorted rather than filtered into groups, so no chore can ever be dropped out of the book by a kind the list
1225
- // above forgot a missing kind sorts to the front, where it is visible, instead of vanishing.
1225
+ // above forgot, a missing kind sorts to the front, where it is visible, instead of vanishing.
1226
1226
  export const CHORES: readonly Chore[] = BOOK.toSorted((left, right) => KIND_ORDER.indexOf(left.kind) - KIND_ORDER.indexOf(right.kind));
1227
1227
 
1228
1228
  export const choreById = (id: string): Chore | undefined => CHORES.find((chore) => chore.id === id);
@@ -1230,7 +1230,7 @@ export const choreById = (id: string): Chore | undefined => CHORES.find((chore)
1230
1230
  // The prompt for one chore against one finding. Built here rather than in the view because the panel, the badge's
1231
1231
  // tooltip and the automation that runs unattended must all be describing the same turn.
1232
1232
  /* THE SCHEDULED TURN, for a chore woken by its automation rather than started from the panel. Same four parts and
1233
- * the same invariants a chore asks for the same work whoever started it with the guard's own report standing
1233
+ * the same invariants, a chore asks for the same work whoever started it, with the guard's own report standing
1234
1234
  * in for the finding, because at 3am there is no verdict to quote and no reader to have checked it first.
1235
1235
  *
1236
1236
  * Workspace-wide rather than per repository: an automation's guard runs at the workspace root on the sandbox's
@@ -1251,7 +1251,7 @@ export const chorePrompt = (chore: Chore, finding: ChoreFinding, repo: string):
1251
1251
  composeAsk({
1252
1252
  subject: `${chore.title} in ${repoLabel(repo)}.`,
1253
1253
  // The RULE before the numbers. An agent told only "4 majors waiting" has to infer why anyone cares; told
1254
- // the criterion it was woken by, it can also tell us the criterion was wrong which is the single most
1254
+ // the criterion it was woken by, it can also tell us the criterion was wrong, which is the single most
1255
1255
  // useful thing a chore turn can report back, and the only way the book gets better.
1256
1256
  why: `${finding.why} You were woken because: ${chore.criterion} ${TRIAGE_NOTE}`,
1257
1257
  diagnosis: chore.diagnosis,
@@ -1,8 +1,8 @@
1
- /* THE EVIDENCE DIGEST the one idea that turns a nagging panel into something you can live with.
1
+ /* THE EVIDENCE DIGEST, the one idea that turns a nagging panel into something you can live with.
2
2
  *
3
3
  * A chore is due because of a measurement. Run the turn, and one of three things happens: the measurement moves
4
4
  * (the work landed), it stays put (the work is pending review, or the tool was wrong), or the tool reports
5
- * something else entirely next week. Only the first is "done", and none of them is answerable by a timestamp
5
+ * something else entirely next week. Only the first is "done", and none of them is answerable by a timestamp,
6
6
  * "ran 3 days ago" cannot tell you whether it ran against THIS.
7
7
  *
8
8
  * So a run records a hash of the evidence that provoked it. The next verdict compares:
@@ -32,7 +32,7 @@ export const digestOf = (...parts: readonly (string | number)[]): string => {
32
32
 
33
33
  /* Buckets a count so that ordinary drift does not read as news. Twelve outdated packages becoming thirteen is not
34
34
  * a thing to interrupt someone about; twelve becoming forty is. Powers-of-two boundaries, so the bucket widens
35
- * with the number the difference between 1 and 2 matters and the difference between 400 and 500 does not.
35
+ * with the number, the difference between 1 and 2 matters and the difference between 400 and 500 does not.
36
36
  *
37
37
  * This is the difference between a digest that changes on every poll (and therefore badges forever) and one that
38
38
  * changes when the situation does. Chores that count things digest the BUCKET; chores whose evidence is a set of
@@ -1,12 +1,12 @@
1
1
  import { composeAsk } from "./prompt.js";
2
2
 
3
- /* AN EXTENSION UPDATE, READ AS A DIFF. The commit that is installed was approved once already re-reading all
3
+ /* AN EXTENSION UPDATE, READ AS A DIFF. The commit that is installed was approved once already, re-reading all
4
4
  * of it would bury the one question an update asks: what is different, and did any of it change the deal? So
5
5
  * the turn's subject is the diff between the two commits, and the manifest's delta leads, because a new entry
6
6
  * in `permissions.sandbox` is reach the owner never approved, arriving dressed as an update.
7
7
  *
8
8
  * Here in the contract's chores rather than in the web app, because two callers build it: the update card's
9
- * "read the diff" button, and the daemon's agent-prepared update policy which runs this exact read
9
+ * "read the diff" button, and the daemon's agent-prepared update policy, which runs this exact read
10
10
  * unprompted when the registry lists a new sha, so the owner opens a finished account instead of starting one. */
11
11
  const UPDATE_INVARIANTS =
12
12
  `This turn reads and reports; it changes nothing and installs nothing. Clone into a scratch directory ` +
@@ -19,7 +19,7 @@ export interface UpdateBrief {
19
19
  // The listing's display name, or the repository when it is being installed straight from a URL.
20
20
  readonly label: string;
21
21
  readonly url: string;
22
- // What is installed and what the update proposes both full shas, both facts, neither a branch.
22
+ // What is installed and what the update proposes, both full shas, both facts, neither a branch.
23
23
  readonly fromRef: string;
24
24
  readonly toRef: string;
25
25
  // Subdirectory inside the repository, for a monorepo source. Empty for a repo of its own.
@@ -1,14 +1,14 @@
1
- /* THE FIX CHORE the template for opting into dependency-breakage repair.
1
+ /* THE FIX CHORE, the template for opting into dependency-breakage repair.
2
2
  *
3
3
  * The automations catalogue offers it as a recipe; nothing creates it until the owner picks that template.
4
4
  *
5
- * It wakes on `deps.broken` the dependency verifier's edge event: a landed change drifted the installed
5
+ * It wakes on `deps.broken`, the dependency verifier's edge event: a landed change drifted the installed
6
6
  * dependencies, the daemon reinstalled them, ran the tree's own checks, and they came back red. The payload
7
7
  * is that event: `deps.project` is the project whose checks failed, `deps.command` the exact command that
8
8
  * judged it, `deps.exitCode` and `deps.logTail` what it said, and `deps.attempt` which consecutive red this
9
9
  * is since the last green.
10
10
  *
11
- * THE GUARD IS THE LOOP CAP, and it lives in guard shell one visible, owner-editable line rather than in
11
+ * THE GUARD IS THE LOOP CAP, and it lives in guard shell, one visible, owner-editable line, rather than in
12
12
  * daemon code, because whoever tunes "how many tries before a human looks" must be able to see the number.
13
13
  * Attempt 1 is the breakage, attempt 2 is one landed fix that still failed; past that the loop stops and the
14
14
  * standing red is the owner's to read (the activity feed has been narrating every step).