@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.
- package/dist/chores/chores.js +1 -1
- package/dist/chores/chores.js.map +1 -1
- package/dist/chores/prompt.d.ts.map +1 -1
- package/dist/chores/prompt.js +3 -3
- package/dist/chores/prompt.js.map +1 -1
- package/dist/contracts/ports.contract.d.ts +11 -0
- package/dist/contracts/ports.contract.d.ts.map +1 -1
- package/dist/index.d.ts +11 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/schemas.d.ts +22 -0
- package/dist/schemas.d.ts.map +1 -1
- package/dist/schemas.js +3 -0
- package/dist/schemas.js.map +1 -1
- package/package.json +4 -4
- package/src/agent-catalog.ts +78 -78
- package/src/agent-run-model.ts +8 -8
- package/src/capability-env.ts +3 -3
- package/src/capability-secrets.ts +5 -5
- package/src/chores/chores.ts +67 -67
- package/src/chores/digest.ts +3 -3
- package/src/chores/extension-update.ts +3 -3
- package/src/chores/fix-deps.ts +3 -3
- package/src/chores/probes.ts +16 -16
- package/src/chores/prompt.ts +9 -9
- package/src/chores/stack.ts +17 -17
- package/src/chores/verdict.ts +23 -23
- package/src/contract-lock.ts +7 -7
- package/src/contracts/activity.contract.ts +1 -1
- package/src/contracts/agent.contract.ts +2 -2
- package/src/contracts/agents.contract.ts +11 -11
- package/src/contracts/automations.contract.ts +5 -5
- package/src/contracts/capabilities.contract.ts +7 -7
- package/src/contracts/chores.contract.ts +3 -3
- package/src/contracts/ci.contract.ts +2 -2
- package/src/contracts/claude.contract.ts +3 -3
- package/src/contracts/drafts.contract.ts +1 -1
- package/src/contracts/endpoints.contract.ts +2 -2
- package/src/contracts/extensions.contract.ts +8 -8
- package/src/contracts/git.contract.ts +6 -6
- package/src/contracts/grok.contract.ts +4 -4
- package/src/contracts/host.contract.ts +5 -5
- package/src/contracts/intentic.contract.ts +2 -2
- package/src/contracts/logs.contract.ts +1 -1
- package/src/contracts/loops.contract.ts +8 -8
- package/src/contracts/personas.contract.ts +8 -8
- package/src/contracts/ports.contract.ts +1 -1
- package/src/contracts/prepush.contract.ts +2 -2
- package/src/contracts/providers.contract.ts +4 -4
- package/src/contracts/public.contract.ts +1 -1
- package/src/contracts/push.contract.ts +2 -2
- package/src/contracts/secrets.contract.ts +2 -2
- package/src/contracts/sessions.contract.ts +2 -2
- package/src/contracts/settings.contract.ts +3 -3
- package/src/contracts/share.contract.ts +1 -1
- package/src/contracts/skills.contract.ts +2 -2
- package/src/contracts/system.contract.ts +9 -9
- package/src/contracts/usage.contract.ts +1 -1
- package/src/contracts/vpn.contract.ts +5 -5
- package/src/contracts/workflows.contract.ts +12 -12
- package/src/contracts/workspace.contract.ts +13 -13
- package/src/conversation-ids.ts +7 -7
- package/src/events.ts +136 -136
- package/src/history-state.ts +12 -12
- package/src/host-protocol.ts +7 -7
- package/src/hostnames.ts +15 -15
- package/src/index.ts +8 -8
- package/src/listener-protocol.ts +12 -12
- package/src/model-order.ts +30 -30
- package/src/output-fields.ts +9 -9
- package/src/path-refs.ts +6 -6
- package/src/publish-drafts.ts +6 -6
- package/src/quick-model.ts +15 -15
- package/src/routes.ts +19 -19
- package/src/runtime-state.ts +22 -22
- package/src/schemas.ts +930 -918
- package/src/search-globs.ts +6 -6
- package/src/session-names.ts +5 -5
- package/src/share-paths.ts +7 -7
- package/src/sse.ts +1 -1
- package/src/state-portability.ts +8 -8
- package/src/terminal-protocol.ts +3 -3
- package/src/title.ts +20 -20
- package/src/tunnel-ids.ts +7 -7
- package/src/versions.ts +5 -5
- package/src/workflow-faults.ts +11 -11
- package/src/workspace-state.ts +119 -119
package/src/chores/chores.ts
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
18
|
-
* advisories, which files
|
|
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
|
|
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
|
|
29
|
-
* accident
|
|
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"
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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"
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
816
|
-
* caught this
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
1015
|
-
* world
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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,
|
package/src/chores/digest.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
/* THE EVIDENCE DIGEST
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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.
|
package/src/chores/fix-deps.ts
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
/* THE FIX CHORE
|
|
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
|
|
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
|
|
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).
|