@intentic/sandbox-contract 1.223.0 → 1.225.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 (165) hide show
  1. package/README.md +14 -14
  2. package/dist/agent-catalog.js +3 -3
  3. package/dist/agent-catalog.js.map +1 -1
  4. package/dist/chores/chores.js +43 -43
  5. package/dist/chores/chores.js.map +1 -1
  6. package/dist/chores/extension-update.js +2 -2
  7. package/dist/chores/extension-update.js.map +1 -1
  8. package/dist/chores/fix-deps.js +1 -1
  9. package/dist/chores/fix-deps.js.map +1 -1
  10. package/dist/chores/probes.js +1 -1
  11. package/dist/chores/probes.js.map +1 -1
  12. package/dist/chores/prompt.d.ts.map +1 -1
  13. package/dist/chores/prompt.js +3 -3
  14. package/dist/chores/prompt.js.map +1 -1
  15. package/dist/chores/verdict.js +2 -2
  16. package/dist/chores/verdict.js.map +1 -1
  17. package/dist/contracts/capabilities.contract.d.ts +41 -0
  18. package/dist/contracts/capabilities.contract.d.ts.map +1 -1
  19. package/dist/contracts/exit.contract.d.ts +80 -0
  20. package/dist/contracts/exit.contract.d.ts.map +1 -0
  21. package/dist/contracts/exit.contract.js +13 -0
  22. package/dist/contracts/exit.contract.js.map +1 -0
  23. package/dist/contracts/ports.contract.d.ts +11 -0
  24. package/dist/contracts/ports.contract.d.ts.map +1 -1
  25. package/dist/contracts/settings.contract.d.ts +12 -0
  26. package/dist/contracts/settings.contract.d.ts.map +1 -1
  27. package/dist/events.d.ts +4 -4
  28. package/dist/events.d.ts.map +1 -1
  29. package/dist/events.js +5 -5
  30. package/dist/events.js.map +1 -1
  31. package/dist/fast-tier.d.ts +9 -0
  32. package/dist/fast-tier.d.ts.map +1 -0
  33. package/dist/fast-tier.js +19 -0
  34. package/dist/fast-tier.js.map +1 -0
  35. package/dist/history-state.js +3 -3
  36. package/dist/history-state.js.map +1 -1
  37. package/dist/index.d.ts +214 -68
  38. package/dist/index.d.ts.map +1 -1
  39. package/dist/index.js +5 -0
  40. package/dist/index.js.map +1 -1
  41. package/dist/model-order.d.ts +1 -0
  42. package/dist/model-order.d.ts.map +1 -1
  43. package/dist/model-order.js +5 -0
  44. package/dist/model-order.js.map +1 -1
  45. package/dist/output-fields.d.ts.map +1 -1
  46. package/dist/output-fields.js +2 -2
  47. package/dist/output-fields.js.map +1 -1
  48. package/dist/prompt-complexity.d.ts +19 -0
  49. package/dist/prompt-complexity.d.ts.map +1 -0
  50. package/dist/prompt-complexity.js +83 -0
  51. package/dist/prompt-complexity.js.map +1 -0
  52. package/dist/publish-drafts.js +2 -2
  53. package/dist/publish-drafts.js.map +1 -1
  54. package/dist/schemas.d.ts +236 -0
  55. package/dist/schemas.d.ts.map +1 -1
  56. package/dist/schemas.js +116 -2
  57. package/dist/schemas.js.map +1 -1
  58. package/dist/workflow-faults.js +3 -3
  59. package/dist/workflow-faults.js.map +1 -1
  60. package/dist/workspace-state.d.ts +20 -20
  61. package/dist/workspace-state.d.ts.map +1 -1
  62. package/dist/workspace-state.js +20 -20
  63. package/dist/workspace-state.js.map +1 -1
  64. package/package.json +5 -5
  65. package/src/agent-catalog.test.ts +25 -25
  66. package/src/agent-catalog.ts +81 -81
  67. package/src/agent-run-model.test.ts +3 -3
  68. package/src/agent-run-model.ts +8 -8
  69. package/src/capability-env.ts +3 -3
  70. package/src/capability-ledger.test.ts +13 -13
  71. package/src/capability-secrets.ts +5 -5
  72. package/src/chores/chores.test.ts +1 -1
  73. package/src/chores/chores.ts +109 -109
  74. package/src/chores/digest.test.ts +1 -1
  75. package/src/chores/digest.ts +3 -3
  76. package/src/chores/extension-update.ts +5 -5
  77. package/src/chores/fix-deps.ts +4 -4
  78. package/src/chores/probes.test.ts +6 -6
  79. package/src/chores/probes.ts +17 -17
  80. package/src/chores/prompt.ts +9 -9
  81. package/src/chores/stack.test.ts +3 -3
  82. package/src/chores/stack.ts +17 -17
  83. package/src/chores/verdict.test.ts +20 -20
  84. package/src/chores/verdict.ts +25 -25
  85. package/src/contract-lock.test.ts +1 -1
  86. package/src/contract-lock.ts +7 -7
  87. package/src/contracts/activity.contract.ts +1 -1
  88. package/src/contracts/agent.contract.ts +2 -2
  89. package/src/contracts/agents.contract.ts +11 -11
  90. package/src/contracts/automations.contract.ts +5 -5
  91. package/src/contracts/capabilities.contract.ts +7 -7
  92. package/src/contracts/chores.contract.ts +3 -3
  93. package/src/contracts/ci.contract.ts +2 -2
  94. package/src/contracts/claude.contract.ts +3 -3
  95. package/src/contracts/drafts.contract.ts +1 -1
  96. package/src/contracts/endpoints.contract.ts +2 -2
  97. package/src/contracts/exit.contract.ts +42 -0
  98. package/src/contracts/extensions.contract.ts +8 -8
  99. package/src/contracts/git.contract.ts +6 -6
  100. package/src/contracts/grok.contract.ts +4 -4
  101. package/src/contracts/host.contract.ts +5 -5
  102. package/src/contracts/intentic.contract.ts +2 -2
  103. package/src/contracts/logs.contract.ts +1 -1
  104. package/src/contracts/loops.contract.ts +8 -8
  105. package/src/contracts/personas.contract.ts +8 -8
  106. package/src/contracts/ports.contract.ts +1 -1
  107. package/src/contracts/prepush.contract.ts +2 -2
  108. package/src/contracts/providers.contract.ts +4 -4
  109. package/src/contracts/public.contract.ts +1 -1
  110. package/src/contracts/push.contract.ts +2 -2
  111. package/src/contracts/secrets.contract.ts +2 -2
  112. package/src/contracts/sessions.contract.ts +2 -2
  113. package/src/contracts/settings.contract.ts +3 -3
  114. package/src/contracts/share.contract.ts +1 -1
  115. package/src/contracts/skills.contract.ts +2 -2
  116. package/src/contracts/system.contract.ts +9 -9
  117. package/src/contracts/usage.contract.ts +1 -1
  118. package/src/contracts/vpn.contract.ts +5 -5
  119. package/src/contracts/workflows.contract.ts +12 -12
  120. package/src/contracts/workspace.contract.ts +13 -13
  121. package/src/conversation-ids.ts +8 -8
  122. package/src/events.test.ts +3 -3
  123. package/src/events.ts +142 -142
  124. package/src/fast-tier.test.ts +88 -0
  125. package/src/fast-tier.ts +72 -0
  126. package/src/history-state.ts +15 -15
  127. package/src/host-protocol.ts +7 -7
  128. package/src/hostnames.test.ts +1 -1
  129. package/src/hostnames.ts +15 -15
  130. package/src/index.ts +13 -8
  131. package/src/listener-protocol.ts +12 -12
  132. package/src/model-order.test.ts +11 -11
  133. package/src/model-order.ts +52 -30
  134. package/src/output-fields.ts +11 -11
  135. package/src/path-refs.test.ts +4 -4
  136. package/src/path-refs.ts +6 -6
  137. package/src/prompt-complexity.test.ts +160 -0
  138. package/src/prompt-complexity.ts +271 -0
  139. package/src/publish-drafts.ts +8 -8
  140. package/src/quick-model.test.ts +11 -11
  141. package/src/quick-model.ts +15 -15
  142. package/src/routes.test.ts +11 -5
  143. package/src/routes.ts +19 -19
  144. package/src/runtime-state.test.ts +1 -1
  145. package/src/runtime-state.ts +22 -22
  146. package/src/schemas.test.ts +8 -8
  147. package/src/schemas.ts +1238 -921
  148. package/src/search-globs.test.ts +2 -2
  149. package/src/search-globs.ts +6 -6
  150. package/src/session-names.ts +5 -5
  151. package/src/share-paths.test.ts +1 -1
  152. package/src/share-paths.ts +7 -7
  153. package/src/sse.ts +1 -1
  154. package/src/state-portability.ts +8 -8
  155. package/src/terminal-protocol.ts +3 -3
  156. package/src/title.test.ts +9 -9
  157. package/src/title.ts +21 -21
  158. package/src/tunnel-ids.test.ts +3 -3
  159. package/src/tunnel-ids.ts +7 -7
  160. package/src/versions.test.ts +3 -3
  161. package/src/versions.ts +6 -6
  162. package/src/workflow-faults.test.ts +6 -6
  163. package/src/workflow-faults.ts +14 -14
  164. package/src/workspace-state.test.ts +32 -32
  165. package/src/workspace-state.ts +139 -139
@@ -13,7 +13,7 @@ describe(`digestOf`, () => {
13
13
  });
14
14
  });
15
15
 
16
- /* The anti-drift mechanism. A chore that counts things must not mint a new digest and therefore a new badge
16
+ /* The anti-drift mechanism. A chore that counts things must not mint a new digest, and therefore a new badge:
17
17
  * every time an ordinary day's work moves the number by one. Buckets widen with the count, because the difference
18
18
  * between one and two matters and the difference between four hundred and five hundred does not. */
19
19
  describe(`bucketOf`, () => {
@@ -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,16 +1,16 @@
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 ` +
13
- `outside the workspace and read the diff between the two commits the installed code was approved once ` +
13
+ `outside the workspace and read the diff between the two commits: the installed code was approved once ` +
14
14
  `already, so what is between them is the whole subject. Lead with the manifest's delta: any route added to ` +
15
15
  `\`permissions.sandbox\` is reach the owner never approved and the headline whatever else changed. Then the ` +
16
16
  `code: what behaviour changed, in the owner's terms, citing file and line.`;
@@ -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.
@@ -33,5 +33,5 @@ export const updateBrief = ({ label, url, fromRef, toRef, path }: UpdateBrief):
33
33
  diagnosis: `The manifest (intentic-extension.json at the extension root) is the contract on both sides of the diff, so its delta is readable exactly like the code's.`,
34
34
  goal: `Read the diff and say what the update actually is: the manifest delta first, then what the code now does that it did not, and what it stopped doing.`,
35
35
  invariants: UPDATE_INVARIANTS,
36
- done: `Done when you end on a recommendation the owner can act on update, update and watch something named, or stay on ${fromRef.slice(0, 7)} with the change that decided it cited by file and line.`,
36
+ done: `Done when you end on a recommendation the owner can act on, update, update and watch something named, or stay on ${fromRef.slice(0, 7)}, with the change that decided it cited by file and line.`,
37
37
  });
@@ -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).
@@ -28,7 +28,7 @@ export const FIX_DEPS_AUTOMATION = {
28
28
  "and they failed. The payload names the project (`deps.project`), the check command (`deps.command`), how it exited " +
29
29
  "(`deps.exitCode`) and the tail of its output (`deps.logTail`); the full log is in the project's `--verify` terminal.\n\n" +
30
30
  "Re-run the check yourself to see the failure first-hand, then fix the ROOT CAUSE. That usually means updating call " +
31
- "sites, types or tests to match what actually changed never loosening or deleting the checks, pinning or downgrading " +
31
+ "sites, types or tests to match what actually changed: never loosening or deleting the checks, pinning or downgrading " +
32
32
  "dependencies just to silence them, or editing generated files. If the breakage needs a decision only the owner can " +
33
33
  "make (an intentional breaking upgrade, a license change, a dependency that should be dropped), stop and say exactly " +
34
34
  "that instead of guessing.\n\n" +
@@ -3,7 +3,7 @@ import { probeSpec } from "./probes.js";
3
3
  import { IDIOM_RULES } from "./stack.js";
4
4
 
5
5
  /* The parsers are the part of this library that faces someone else's output, so they are tested the way that
6
- * output actually arrives: real shapes, then the shapes that have historically broken things a tool that
6
+ * output actually arrives: real shapes, then the shapes that have historically broken things, a tool that
7
7
  * printed a warning line before its JSON, a version whose fields moved, an empty run. The bar for every one of
8
8
  * them is the same: recognise it, or return undefined so the runner can record a failure. Never throw, and never
9
9
  * report a clean result from output it did not understand. */
@@ -135,7 +135,7 @@ describe(`knip`, () => {
135
135
  });
136
136
  });
137
137
 
138
- // Without an `issues` array this is not knip's report, whatever else it contains and reporting zero dead code
138
+ // Without an `issues` array this is not knip's report, whatever else it contains, and reporting zero dead code
139
139
  // from a shape we do not recognise is exactly the lie the state machine exists to prevent.
140
140
  test(`a shape without an issues array is a failure`, () => {
141
141
  expect(parse(`knip`, JSON.stringify({ files: [`src/old.ts`] }))).toBeUndefined();
@@ -233,8 +233,8 @@ describe(`ui`, () => {
233
233
  });
234
234
 
235
235
  /* Every path the sweep prints wears a `./`, because every ripgrep in it is handed `.` to walk. Downstream this
236
- * would have to be remembered at each comparison jscpd's paths against the component list, a bypass against
237
- * a component so it is spent once, here, and one spelling of a path leaves the parser. */
236
+ * would have to be remembered at each comparison: jscpd's paths against the component list, a bypass against
237
+ * a component, so it is spent once, here, and one spelling of a path leaves the parser. */
238
238
  test(`strips the prefix ripgrep prints for a path it was told to walk`, () => {
239
239
  expect(parse(`ui`, sweep(`COMPONENT\t./src/Button.vue`, `BYPASS\t./src/Button.vue:3`, `IDIOM\tvue-options-api\t./src/Old.vue`))).toEqual({
240
240
  id: `ui`,
@@ -257,7 +257,7 @@ describe(`ui`, () => {
257
257
  describe(`the sweep's composed command`, () => {
258
258
  const stages = (): string[] => probeSpec(`ui`).command.split(`; `);
259
259
 
260
- /* Given no path, ripgrep searches STDIN whenever stdin is not a TTY which is exactly how a probe is spawned.
260
+ /* Given no path, ripgrep searches STDIN whenever stdin is not a TTY, which is exactly how a probe is spawned.
261
261
  * The sweep exited 0, printed its marker and matched nothing, in every repository, forever, which the marker
262
262
  * line cannot catch because the sweep really did run. It reproduces from a child process and never from an
263
263
  * interactive shell, so the command is the only place it is visible. */
@@ -300,7 +300,7 @@ describe(`bundle`, () => {
300
300
  });
301
301
 
302
302
  // The `find` prints nothing for a directory that exists but holds no assets. `available` is supposed to catch
303
- // that, and this is the second line of defence a zero-byte bundle would otherwise read as a fact.
303
+ // that, and this is the second line of defence: a zero-byte bundle would otherwise read as a fact.
304
304
  test(`a directory line with no assets is an empty build, not a failure`, () => {
305
305
  expect(parse(`bundle`, `DIR\tbuild`)).toMatchObject({ bundle: { dir: `build`, assets: [], totalBytes: 0, totalGzip: 0 } });
306
306
  });
@@ -11,19 +11,19 @@ import {
11
11
  TAILWIND_PACKAGES,
12
12
  } from "./stack.js";
13
13
 
14
- /* THE PROBES the measurements that cost a subprocess, declared once so the daemon that runs them and the panel
14
+ /* THE PROBES, the measurements that cost a subprocess, declared once so the daemon that runs them and the panel
15
15
  * that explains them cannot disagree about what "outdated" meant.
16
16
  *
17
17
  * A spec is a shell command and a parser, deliberately in that order of trust: the command is whatever the tool's
18
18
  * own maintainers publish as its machine-readable output, and the parser is written to be DISAPPOINTED. Every one
19
19
  * of these tools has changed its JSON shape at least once, they are run against whatever version the repo pinned,
20
20
  * and a probe that throws on an unexpected field would take the whole maintenance surface down with it. So each
21
- * parser walks the structure defensively and returns `undefined` when it cannot recognise what it got which the
21
+ * parser walks the structure defensively and returns `undefined` when it cannot recognise what it got, which the
22
22
  * runner records as a failed probe with the output attached, rather than as a clean repository.
23
23
  *
24
24
  * TIERS ARE ABOUT COST, and the cost is what sets the cadence. Tier 1 reads metadata that already exists (a
25
25
  * lockfile, a registry's version list) and finishes in seconds, so the background runner refreshes it daily. Tier
26
- * 2 reads the whole tree knip type-checks it, jscpd tokenizes every file and can run for minutes on a large
26
+ * 2 reads the whole tree, knip type-checks it, jscpd tokenizes every file, and can run for minutes on a large
27
27
  * repo, so it refreshes weekly and says how long it took, because a reader deciding whether to force a refresh
28
28
  * deserves to know what they are asking for.
29
29
  *
@@ -48,7 +48,7 @@ export interface ProbeSpec {
48
48
  // Exit 0 ⇒ this repo can be measured. Runs in the repo's own directory, like the command.
49
49
  readonly available: string;
50
50
  /* What is MISSING when `available` says no, named here rather than derived from the title. The obvious
51
- * derivation "this repository has no security advisories to measure" states the one thing an unmeasured
51
+ * derivation, "this repository has no security advisories to measure", states the one thing an unmeasured
52
52
  * probe must never claim, that there are none, and it is the same conflation the block above exists to
53
53
  * prevent. Phrased as a bare clause ("no lockfile"), because the panel groups these under its own lead-in. */
54
54
  readonly unavailable: string;
@@ -97,7 +97,7 @@ const semverKind = (current: string, latest: string): OutdatedPackage["kind"] =>
97
97
  /* `pnpm outdated --json` prints a map of package name → { current, latest, dependencyType }. In a workspace the
98
98
  * recursive form merges every package's entries into the same map and adds `dependentPackages`, which is why this
99
99
  * reads the map rather than expecting a list: one shape covers both, and a field we don't use appearing is not a
100
- * parse failure. Entries missing `current` or `latest` are skipped that is how pnpm reports a package it could
100
+ * parse failure. Entries missing `current` or `latest` are skipped, that is how pnpm reports a package it could
101
101
  * not resolve against the registry, and it is not evidence of anything. */
102
102
  const parseOutdated = (stdout: string): ProbeFacts | undefined => {
103
103
  const root = asObject(stdout);
@@ -123,8 +123,8 @@ const parseOutdated = (stdout: string): ProbeFacts | undefined => {
123
123
  const SEVERITIES = new Set([`critical`, `high`, `moderate`, `low`, `info`]);
124
124
 
125
125
  /* `pnpm audit --json` prints `{ advisories: { <id>: {...} }, metadata: {...} }`. The metadata's counts are
126
- * deliberately ignored: they are a tally, and this surface needs the advisories themselves which package, and
127
- * whether a patched range exists because "there is a fix that is a version bump" and "there is no patch yet"
126
+ * deliberately ignored: they are a tally, and this surface needs the advisories themselves, which package, and
127
+ * whether a patched range exists, because "there is a fix that is a version bump" and "there is no patch yet"
128
128
  * lead to completely different turns, and a count cannot tell them apart.
129
129
  *
130
130
  * `dev` comes off the findings' own flag rather than being inferred. A build-time-only advisory is real but it is
@@ -135,7 +135,7 @@ const parseAudit = (stdout: string): ProbeFacts | undefined => {
135
135
  return undefined;
136
136
  }
137
137
  const raw = root[`advisories`];
138
- // No `advisories` key at all is pnpm's clean report an empty list, not an unrecognisable one.
138
+ // No `advisories` key at all is pnpm's clean report, an empty list, not an unrecognisable one.
139
139
  if (raw === undefined) {
140
140
  return { id: `audit`, advisories: [] };
141
141
  }
@@ -168,7 +168,7 @@ const parseAudit = (stdout: string): ProbeFacts | undefined => {
168
168
  return { id: `audit`, advisories };
169
169
  };
170
170
 
171
- /* knip's JSON reporter prints `{ issues: [...] }` one row per file that has findings, carrying a per-kind array
171
+ /* knip's JSON reporter prints `{ issues: [...] }`, one row per file that has findings, carrying a per-kind array
172
172
  * of what it found there. A wholly unreferenced file is a row whose own `files` array names it, which is why that
173
173
  * count is a sum like every other kind rather than a list of its own. Counts plus a sample of the file paths, not
174
174
  * the full list: the agent re-runs knip itself against the live tree (a list from a probe hours old would send it
@@ -233,16 +233,16 @@ const parseJscpd = (stdout: string): ProbeFacts | undefined => {
233
233
  * Everything it emits is a labelled, tab-separated line, and the first line is always the bare marker `UI`. That
234
234
  * marker is the whole reason this parser can tell "the sweep ran and this repository is clean" from "the sweep
235
235
  * never ran": every other line is optional, so without it an empty stdout and a spotless codebase are the same
236
- * string and reporting the second when it was the first is the one thing probes.ts exists to prevent. */
236
+ * string, and reporting the second when it was the first is the one thing probes.ts exists to prevent. */
237
237
  const UI_MARKER = `UI`;
238
- // Caps, applied after `sort` so truncation is alphabetical and therefore identical between runs an unsorted
238
+ // Caps, applied after `sort` so truncation is alphabetical and therefore identical between runs, an unsorted
239
239
  // truncation would mint a new digest on every sweep and badge forever. What is dropped is genuinely dropped: a
240
240
  // component past the cap cannot join a family, and the chore says so rather than implying it saw everything.
241
241
  const COMPONENT_LIMIT = 2000;
242
242
  const RULE_FILE_LIMIT = 500;
243
243
 
244
244
  /* THE `.` IS LOAD-BEARING, and leaving it off cost this probe every finding it will ever have. Given no path,
245
- * ripgrep searches the tree only when stdin is a TTY otherwise it reads STDIN, which is exactly how the runner
245
+ * ripgrep searches the tree only when stdin is a TTY, otherwise it reads STDIN, which is exactly how the runner
246
246
  * spawns a probe. The sweep therefore ran, exited 0, printed its marker and matched nothing, in every repository,
247
247
  * forever: the precise failure the marker line was introduced to make impossible, arriving through the one door it
248
248
  * does not cover. It reproduces from Node and not from an interactive shell, which is why it survived being read.
@@ -367,7 +367,7 @@ const parseBundle = (stdout: string): ProbeFacts | undefined => {
367
367
  };
368
368
 
369
369
  // Where the tier-2 tools leave their reports. Under /tmp because they are inputs to a parse that happens
370
- // immediately after, never something to keep the cached ProbeResult is the artefact that survives. The same
370
+ // immediately after, never something to keep, the cached ProbeResult is the artefact that survives. The same
371
371
  // path the scheduled form of this chore uses (chores.ts), so a workspace running both keeps one copy.
372
372
  const JSCPD_DIR = `/tmp/intentic-chore-jscpd`;
373
373
 
@@ -423,7 +423,7 @@ export const PROBES: readonly ProbeSpec[] = [
423
423
  timeoutMs: 20 * 60_000,
424
424
  available: `test -f package.json`,
425
425
  unavailable: `no package.json`,
426
- // `--threshold 100` so jscpd never fails the command on its own opinion of what is too much duplication
426
+ // `--threshold 100` so jscpd never fails the command on its own opinion of what is too much duplication,
427
427
  // that judgement is the chore's, made from the percentage, not the tool's exit code.
428
428
  command:
429
429
  `pnpm dlx jscpd --reporters json --output ${JSCPD_DIR} --min-lines 12 --threshold 100 . >/dev/null 2>&1; ` +
@@ -437,11 +437,11 @@ export const PROBES: readonly ProbeSpec[] = [
437
437
  /* Tier 1 despite reading the whole tree, and the placement is a judgement rather than an oversight. The
438
438
  * tier is about COST: this is a dozen ripgrep walks, seconds on a large monorepo, against knip
439
439
  * type-checking the tree and jscpd tokenizing every file for minutes. A weekly TTL would also make it the
440
- * wrong shape its findings move whenever someone writes a component, which is daily. */
440
+ * wrong shape, its findings move whenever someone writes a component, which is daily. */
441
441
  tier: 1,
442
442
  ttlMs: DAY_MS,
443
443
  timeoutMs: 5 * 60_000,
444
- // Any manifest in the repo declaring a UI framework or Tailwind, not just the root's a monorepo keeps
444
+ // Any manifest in the repo declaring a UI framework or Tailwind, not just the root's, a monorepo keeps
445
445
  // React in the app package and the root manifest is a handful of build tools.
446
446
  available:
447
447
  `rg -l --no-messages -g '**/package.json' -g '!**/node_modules/**' ` +
@@ -463,7 +463,7 @@ export const PROBES: readonly ProbeSpec[] = [
463
463
  available: `find ${BUILD_DIRS.join(` `)} -maxdepth 4 -type f \\( -name '*.js' -o -name '*.mjs' -o -name '*.css' \\) 2>/dev/null | head -n 1 | grep -q .`,
464
464
  // Says what is missing AND that this never builds, because the obvious reading of "no build output" is
465
465
  // that we tried and it failed. The owner running their own build once is the whole fix.
466
- unavailable: `no build output on disk this reads the last build, it never runs one`,
466
+ unavailable: `no build output on disk, this reads the last build, it never runs one`,
467
467
  command: bundleCommand(),
468
468
  parse: parseBundle,
469
469
  },
@@ -1,18 +1,18 @@
1
- /* HOW WE ASK. Every prompt this workspace generates from a measurement a hotspot's refactor, a chore's sweep
1
+ /* HOW WE ASK. Every prompt this workspace generates from a measurement, a hotspot's refactor, a chore's sweep,
2
2
  * has the same four parts, in the same order, for the same reasons:
3
3
  *
4
4
  * subject the one line that says what is being worked on. First, because a model that reads the rationale
5
5
  * before the target starts planning against a subject it has not been told yet.
6
6
  * why the NUMBERS, quoted exactly as the panel shows them, then what they mean. Exact so the agent and
7
7
  * the person are arguing about one set of facts; the agent can and should recount them.
8
- * goal what shape to move towards never a design. Whoever generated this prompt has not read the code,
8
+ * goal what shape to move towards, never a design. Whoever generated this prompt has not read the code,
9
9
  * so a prescribed solution from out here is a guess wearing an instruction's clothes.
10
10
  * done falsifiable, and checkable by the agent itself. The same resident engine that produced the
11
11
  * measurement answers `iq` in the agent's own worktree, so "run it again and see" is available and
12
12
  * "I have finished" is not something it has to be taken at its word on.
13
13
  *
14
14
  * The invariants sit between goal and done because they are the constraints on HOW, and they are stated in full
15
- * every time rather than assumed. Each one is a specific way the turn fails without it they are here because
15
+ * every time rather than assumed. Each one is a specific way the turn fails without it, they are here because
16
16
  * they were each learned from a diff nobody could review. */
17
17
 
18
18
  export interface Ask {
@@ -30,16 +30,16 @@ export const composeAsk = ({ subject, why, diagnosis, goal, invariants, done }:
30
30
  /* Said to every turn a TOOL woke, and the reason the maintenance surface can point agents at tool output at all.
31
31
  * A tool reporting N findings is not reporting N problems: knip is confidently wrong about anything reachable
32
32
  * from outside the repo, jscpd counts generated files, an advisory in a build-time dependency is not the same
33
- * risk as one in a running service. A chore that mechanically actions the whole list is worse than no chore
33
+ * risk as one in a running service. A chore that mechanically actions the whole list is worse than no chore,
34
34
  * it makes noisy, confident, wrong changes at three in the morning, and the next person has to review a diff
35
35
  * whose author had no opinion about it. */
36
36
  export const TRIAGE_NOTE =
37
37
  `The measurement woke you; it did not decide anything. Read the repository before you touch it, and treat every ` +
38
38
  `finding as a claim to verify rather than a task to execute. If a finding is wrong, say why in one line and leave ` +
39
- `it a run that verifies ten and fixes two is a good run.`;
39
+ `it. A run that verifies ten and fixes two is a good run.`;
40
40
 
41
41
  /* The invariants for a turn that CHANGES things. Whatever it does lands as uncommitted work in the owner's
42
- * workspace, so it is reviewed as one diff by someone who did not watch it happen which is what every clause
42
+ * workspace, so it is reviewed as one diff by someone who did not watch it happen, which is what every clause
43
43
  * here is protecting.
44
44
  *
45
45
  * "Separately explainable" is doing the most work: a chore that fixes its findings AND tidies what it passed on
@@ -57,8 +57,8 @@ export const REPORT_INVARIANTS =
57
57
  `can act on or dismiss. Where you would propose an edit, describe it and where it would go instead of making it.`;
58
58
 
59
59
  /* The invariants for a turn refactoring ONE FILE, as the codebase-health panel's rows ask for. Distinct from the
60
- * chore ones because the blast radius is the thing at stake: named as a radius rather than "only this file",
60
+ * chore ones because the scope of changes is the thing at stake: named as a scope rather than "only this file",
61
61
  * since half those archetypes ask for new files and must not read as forbidding them. */
62
62
  export const REFACTOR_INVARIANTS =
63
- `Read it first. Behaviour stays identical, and the blast radius is this file, whatever it splits into, and the ` +
64
- `importers that must follow no re-export shims left behind.`;
63
+ `Read it first. Behaviour stays identical. Changes affect only this file, whatever it splits into, and the ` +
64
+ `importers that must follow. Leave no re-export shims behind.`;
@@ -5,7 +5,7 @@ import { componentStem, frameworksOf, IDIOM_RULES, idiomRule, UI_FRAMEWORKS, use
5
5
  *
6
6
  * Most of what is below guards a failure that CANNOT be seen by reading the table: a pattern is interpolated into
7
7
  * a shell command that runs on someone else's machine at three in the morning, so a stray quote is not a typo
8
- * anyone reviews it is a probe that dies in a workspace nobody is watching, with a shell error for a reason. */
8
+ * anyone reviews: it is a probe that dies in a workspace nobody is watching, with a shell error for a reason. */
9
9
 
10
10
  describe(`the patterns are safe to interpolate`, () => {
11
11
  // The scan wraps every pattern and glob in shell single quotes. One apostrophe inside ends the quoting and
@@ -29,7 +29,7 @@ describe(`the patterns are safe to interpolate`, () => {
29
29
 
30
30
  /* Rust's regex crate has no lookaround, and getting it means ripgrep's -P, which is a compile-time option on
31
31
  * the box the sweep happens to run on. A rule that seems to need one is asking a question about the FILE
32
- * rather than about a line which is what `absent` is. */
32
+ * rather than about a line, which is what `absent` is. */
33
33
  test(`no pattern uses a lookaround`, () => {
34
34
  for (const rule of IDIOM_RULES) {
35
35
  expect(rule.pattern, rule.id).not.toMatch(/\(\?<?[=!]/);
@@ -109,7 +109,7 @@ describe(`the name two components share`, () => {
109
109
  });
110
110
 
111
111
  /* The trap in stripping a trailing number. `H1` and `H2` are different components and reduce to the same
112
- * single letter, so the stem is only accepted when what survives is still long enough to mean something
112
+ * single letter, so the stem is only accepted when what survives is still long enough to mean something:
113
113
  * otherwise the untouched name is kept and the two stay apart. */
114
114
  test(`short names keep their digits rather than collapsing together`, () => {
115
115
  expect(componentStem(`src/type/H1.tsx`)).toBe(`h1`);
@@ -1,4 +1,4 @@
1
- /* WHAT THIS REPOSITORY IS BUILT WITH, and the patterns that follow from it the table the UI chores are written
1
+ /* WHAT THIS REPOSITORY IS BUILT WITH, and the patterns that follow from it, the table the UI chores are written
2
2
  * against, kept apart from both the probe that runs it and the chores that read it.
3
3
  *
4
4
  * It sits in the middle on purpose. probes.ts composes ONE ripgrep sweep out of the rules below, and chores.ts
@@ -13,14 +13,14 @@
13
13
  * THE PATTERNS ARE RIPGREP'S DIALECT, and they carry two constraints that are not obvious from reading them:
14
14
  *
15
15
  * No literal apostrophe, ever. The scan command wraps each pattern in shell single quotes, so a `'` inside one
16
- * would end the quoting and hand the rest of the regex to the shell. Match quotes as `[\x22\x27]` instead
16
+ * would end the quoting and hand the rest of the regex to the shell. Match quotes as `[\x22\x27]` instead,
17
17
  * Rust's regex crate reads those escapes, and the shell never sees a quote character at all. stack.test.ts
18
18
  * enforces this, because the failure is a probe that dies at three in the morning in someone else's workspace
19
19
  * rather than anything a reader would notice here.
20
20
  *
21
21
  * No lookaround. Rust's regex crate has none, and reaching for ripgrep's PCRE2 mode to get it would make the
22
22
  * sweep depend on how the box's ripgrep was compiled. A rule that seems to need it is usually asking a question
23
- * about the FILE rather than about a line see `absent` below, which is what that question actually is. */
23
+ * about the FILE rather than about a line, see `absent` below, which is what that question actually is. */
24
24
 
25
25
  export interface UiFramework {
26
26
  readonly id: string;
@@ -38,7 +38,7 @@ export const UI_FRAMEWORKS: readonly UiFramework[] = [
38
38
  { id: `angular`, label: `Angular`, packages: [`@angular/core`] },
39
39
  ];
40
40
 
41
- // Tailwind is not in the table above because it is not a UI framework and does not own any idiom rules it is a
41
+ // Tailwind is not in the table above because it is not a UI framework and does not own any idiom rules, it is a
42
42
  // styling system that any of the three can be wearing, and it gates exactly one chore.
43
43
  export const TAILWIND_PACKAGES: readonly string[] = [`tailwindcss`];
44
44
 
@@ -64,20 +64,20 @@ export const SCAN_IGNORES: readonly string[] = [
64
64
  `!**/*.{test,spec,stories}.*`,
65
65
  ];
66
66
 
67
- // What counts as a component file, across all three frameworks at once. The sweep cannot vary by repository a
68
- // probe's command is a fixed string so it asks for all of them and a Vue-only repo simply has no `.tsx` files.
67
+ // What counts as a component file, across all three frameworks at once. The sweep cannot vary by repository, a
68
+ // probe's command is a fixed string, so it asks for all of them and a Vue-only repo simply has no `.tsx` files.
69
69
  export const COMPONENT_GLOBS: readonly string[] = [`*.vue`, `*.tsx`, `*.jsx`, `*.component.ts`];
70
70
 
71
71
  // Where a Tailwind class can appear. Wider than COMPONENT_GLOBS because a class list lives in markup as often as
72
- // in a component an Angular template and a plain .html page both style with the same utilities.
72
+ // in a component, an Angular template and a plain .html page both style with the same utilities.
73
73
  export const MARKUP_GLOBS: readonly string[] = [`*.vue`, `*.tsx`, `*.jsx`, `*.html`, `*.svelte`, `*.astro`];
74
74
 
75
- /* THE DESIGN SYSTEM BYPASS. Not "any arbitrary value" `grid-cols-[1fr_auto]` and `w-[calc(100%-2rem)]` are
75
+ /* THE DESIGN SYSTEM BYPASS. Not "any arbitrary value", `grid-cols-[1fr_auto]` and `w-[calc(100%-2rem)]` are
76
76
  * Tailwind working as designed, and a chore that counted them would be objecting to the feature rather than to
77
77
  * anything wrong. What this matches is the two arbitrary values that route around a decision the theme already
78
78
  * made: a colour that is not in the palette, and a pixel size that is not on the spacing or type scale.
79
79
  *
80
- * The leading `-` is load-bearing. It anchors the match to a utility prefix (`bg-`, `text-`, `w-`), so a bare
80
+ * The leading `-` is required. It anchors the match to a utility prefix (`bg-`, `text-`, `w-`), so a bare
81
81
  * `[...]` in ordinary prose or an array index cannot be mistaken for a class. */
82
82
  export const BYPASS_PATTERN = `-\\[(#[0-9a-fA-F]{3,8}|(rgb|hsl)a?\\(|[0-9]+(\\.[0-9]+)?px)`;
83
83
 
@@ -87,12 +87,12 @@ export interface IdiomRule {
87
87
  readonly framework: string;
88
88
  // What the repository still has, named as the reader would name it.
89
89
  readonly label: string;
90
- // What replaced it. Carried so the prompt can say where to go rather than only what to leave an agent told
90
+ // What replaced it. Carried so the prompt can say where to go rather than only what to leave, an agent told
91
91
  // "you still use NgModule" and nothing else will pick a destination, and it may not pick this one.
92
92
  readonly replacement: string;
93
93
  readonly pattern: string;
94
94
  readonly globs: readonly string[];
95
- /* THE IDIOM IS THE PATTERN BEING MISSING, not present `pattern` names the NEW way, and the file is on the
95
+ /* THE IDIOM IS THE PATTERN BEING MISSING, not present, `pattern` names the NEW way, and the file is on the
96
96
  * old one precisely because the new one does not appear in it anywhere. The sweep spells this
97
97
  * `--files-without-match`.
98
98
  *
@@ -100,7 +100,7 @@ export interface IdiomRule {
100
100
  * <script setup>" was first written as a lookahead over `<script`, which matches per LINE: a migrated
101
101
  * component with a second plain `<script>` block for defineOptions, or one that merely mentions `<script` in
102
102
  * a comment, both read as un-migrated. It reported five files in an application whose 167 SFCs are every one
103
- * of them migrated. The question was never "is there a line like this" it is "does this file contain the
103
+ * of them migrated. The question was never "is there a line like this", it is "does this file contain the
104
104
  * new idiom at all", which is one flag rather than a cleverer regex, and it costs no PCRE2.
105
105
  *
106
106
  * The globs carry more weight on an absent rule than on a normal one, and narrowly is the only safe way to
@@ -110,7 +110,7 @@ export interface IdiomRule {
110
110
  }
111
111
 
112
112
  /* THE IDIOMS THEIR OWN MAINTAINERS HAVE MOVED ON FROM. Every rule here names something the framework's own
113
- * documentation now steers people away from, and every one of them still works which is exactly why they
113
+ * documentation now steers people away from, and every one of them still works, which is exactly why they
114
114
  * accumulate, and why no editor and no linter will bring them up unprompted.
115
115
  *
116
116
  * High confidence over coverage. Each pattern is one a reader can check by eye against a file, and the ones that
@@ -156,7 +156,7 @@ export const IDIOM_RULES: readonly IdiomRule[] = [
156
156
  framework: `vue`,
157
157
  label: `the Options API`,
158
158
  replacement: `<script setup> with the Composition API`,
159
- // The new idiom, inverted by `absent` below an SFC that never opens a `<script setup>` tag is still on
159
+ // The new idiom, inverted by `absent` below, an SFC that never opens a `<script setup>` tag is still on
160
160
  // the old one. A file with no script block at all is swept up too, and that is the honest reading: it has
161
161
  // not been migrated because there was nothing there to migrate.
162
162
  pattern: `<script[^>]*\\bsetup\\b`,
@@ -213,14 +213,14 @@ export const idiomRule = (id: string): IdiomRule | undefined => IDIOM_RULES.find
213
213
  * side had two extra characters. */
214
214
  export const normalizePath = (path: string): string => path.replace(/^\.\//, ``);
215
215
 
216
- // Below this a stem is too short to have survived the stripping above with its meaning intact `H1` and `H2`
216
+ // Below this a stem is too short to have survived the stripping above with its meaning intact, `H1` and `H2`
217
217
  // would both reduce to `h` and read as one family of heading components that are not duplicates of anything.
218
218
  const MIN_STEM = 3;
219
219
 
220
220
  const QUALIFIER_PREFIX = /^(base|the)/;
221
221
  const QUALIFIER_SUFFIX = /(v[0-9]+|new|old|legacy|copy|component|[0-9]+)$/;
222
222
 
223
- /* THE NAME TWO COMPONENTS SHARE WHEN THEY ARE THE SAME COMPONENT TWICE or `undefined` when the file has no
223
+ /* THE NAME TWO COMPONENTS SHARE WHEN THEY ARE THE SAME COMPONENT TWICE, or `undefined` when the file has no
224
224
  * name worth comparing.
225
225
  *
226
226
  * This is a normaliser, not a similarity score, and that is the point: it answers a question the reader can check
@@ -232,7 +232,7 @@ const QUALIFIER_SUFFIX = /(v[0-9]+|new|old|legacy|copy|component|[0-9]+)$/;
232
232
  * forty index files is a finding about the naming convention rather than about any duplication. */
233
233
  export const componentStem = (path: string): string | undefined => {
234
234
  const file = normalizePath(path).split(`/`).pop() ?? ``;
235
- // `.component.ts` loses both suffixes, `.vue` loses one taking everything before the first dot handles both
235
+ // `.component.ts` loses both suffixes, `.vue` loses one, taking everything before the first dot handles both
236
236
  // without a table, since a component's name is never the part after a dot.
237
237
  const base = (file.split(`.`)[0] ?? ``).toLowerCase().replace(/[^a-z0-9]/g, ``);
238
238
  if (base === `` || base === `index`) {