@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
@@ -20,7 +20,7 @@ const pkg = (over: Partial<ChorePackage> = {}): ChorePackage => ({
20
20
  ...over,
21
21
  });
22
22
 
23
- // A repository that is a Node workspace with documents, a pipeline, an image and a Tailwind front-end so every
23
+ // A repository that is a Node workspace with documents, a pipeline, an image and a Tailwind front-end, so every
24
24
  // chore APPLIES by default and each applicability test can turn off exactly the one fact it is about.
25
25
  const shape = (over: Partial<ChoreShape> = {}): ChoreShape => ({
26
26
  docs: [`docs/architecture/repo.md`],
@@ -56,7 +56,7 @@ const auditProbe = (names: readonly string[]): ProbeResult =>
56
56
  const report = (over: Partial<ChoresReport> = {}): ChoresReport => ({
57
57
  repos: [{ repo: `app`, probes: [], signals: signals() }],
58
58
  ledger: [],
59
- // Verdicts are about EVIDENCE, never about work in flight a probe running does not make a chore more or
59
+ // Verdicts are about EVIDENCE, never about work in flight: a probe running does not make a chore more or
60
60
  // less due, it only makes the panel say so. Empty here because no assertion in this file should depend on it.
61
61
  running: [],
62
62
  node: `v24.18.0`,
@@ -111,7 +111,7 @@ describe(`the ledger debounces; it cannot hide`, () => {
111
111
  ...over,
112
112
  });
113
113
 
114
- test(`a run against this exact evidence leaves the chore due but settled shown, never badged`, () => {
114
+ test(`a run against this exact evidence leaves the chore due but settled: shown, never badged`, () => {
115
115
  const verdict = verdictFor({ ...withAdvisories, ledger: [ledgerEntry()] }, `security-advisories`);
116
116
  expect(verdict.state).toBe(`due`);
117
117
  expect(verdict.settled).toBe(true);
@@ -134,7 +134,7 @@ describe(`the ledger debounces; it cannot hide`, () => {
134
134
  test(`an agent reporting the findings did not hold up clears the chore until the evidence changes`, () => {
135
135
  const verdict = verdictFor({ ...withAdvisories, ledger: [ledgerEntry({ outcome: `clean` })] }, `security-advisories`);
136
136
  expect(verdict.state).toBe(`clear`);
137
- expect(verdict.headline).toBe(`Checked the findings did not hold up`);
137
+ expect(verdict.headline).toBe(`Checked, the findings did not hold up`);
138
138
  });
139
139
 
140
140
  test(`a snooze silences a due chore without hiding it, and lapses on its own`, () => {
@@ -148,8 +148,8 @@ describe(`the ledger debounces; it cannot hide`, () => {
148
148
  });
149
149
 
150
150
  /* A chore with a cadence expires its own settlement, so "we looked and chose not to act" cannot silence it
151
- * for good. Security has no cadence on purpose an advisory does not become interesting again because
152
- * ninety days passed, it becomes interesting when the advisory set changes so its settlement persists. */
151
+ * for good. Security has no cadence on purpose: an advisory does not become interesting again because
152
+ * ninety days passed, it becomes interesting when the advisory set changes, so its settlement persists. */
153
153
  test(`settlement expires with the chore's cadence, and persists for the chores that have none`, () => {
154
154
  const dependencies = choreById(`dependencies-outdated`);
155
155
  expect(dependencies?.cadenceMs).toBeGreaterThan(0);
@@ -180,7 +180,7 @@ describe(`a measurement older than the work is not evidence about the work`, ()
180
180
  test(`a turn that landed after the measurement steps the chore down from due`, () => {
181
181
  const verdict = verdictFor({ ...withAdvisories, ledger: [ledgerEntry({ ranAt: NOW - 3_600_000 })] }, `security-advisories`);
182
182
  expect(verdict.state).toBe(`stale`);
183
- // The evidence stays on the row it is what the reader checks the claim against and the claim comes off.
183
+ // The evidence stays on the row: it is what the reader checks the claim against, and the claim comes off.
184
184
  expect(verdict.detail).not.toEqual([]);
185
185
  expect(verdict.settled).toBe(false);
186
186
  });
@@ -194,7 +194,7 @@ describe(`a measurement older than the work is not evidence about the work`, ()
194
194
  });
195
195
 
196
196
  // Re-measuring is the whole cure: the same run, against evidence taken after it, is settled rather than stale.
197
- test(`re-measuring after the turn restores the verdict due, and now genuinely settled`, () => {
197
+ test(`re-measuring after the turn restores the verdict: due, and now genuinely settled`, () => {
198
198
  const remeasured = report({
199
199
  repos: [{ repo: `app`, probes: [{ ...auditProbe([`left-pad`]), ranAt: NOW - 60_000 }], signals: signals() }],
200
200
  ledger: [ledgerEntry({ ranAt: NOW - 3_600_000 })],
@@ -223,7 +223,7 @@ describe(`a measurement older than the work is not evidence about the work`, ()
223
223
  // Every measured row carries when it was taken, so no row can pass off a week-old count as this morning's.
224
224
  test(`every measured verdict says when it was measured, and the unmeasurable ones say nothing`, () => {
225
225
  expect(verdictFor(withAdvisories, `security-advisories`).measuredAt).toBe(NOW - DAY);
226
- // A survey rests on no measurement it is decided by the calendar, and has nothing to be out of date.
226
+ // A survey rests on no measurement: it is decided by the calendar, and has nothing to be out of date.
227
227
  expect(verdictFor(report(), `standardize-patterns`).measuredAt).toBeUndefined();
228
228
  // Nor does a probe that never ran.
229
229
  expect(verdictFor(report(), `security-advisories`).measuredAt).toBeUndefined();
@@ -363,7 +363,7 @@ describe(`the prompts`, () => {
363
363
  const dueVerdict = () =>
364
364
  verdictFor(report({ repos: [{ repo: `app`, probes: [auditProbe([`left-pad`])], signals: signals() }] }), `security-advisories`);
365
365
 
366
- /* A prompt that counts without NAMING sends the agent off to re-derive a list we are already holding slowly,
366
+ /* A prompt that counts without NAMING sends the agent off to re-derive a list we are already holding: slowly,
367
367
  * and against a tree that has moved since. Every measured chore names its artefacts. */
368
368
  test(`name the artefacts, not just how many there were`, () => {
369
369
  const verdict = dueVerdict();
@@ -420,7 +420,7 @@ describe(`the prompts`, () => {
420
420
  expect(verdict.prompt, verdict.chore.id).toBeTypeOf(`string`);
421
421
  expect(verdict.digest, verdict.chore.id).not.toBe(``);
422
422
  }
423
- // Each measured chore's own artefact reaches its own prompt the regression this whole test exists for.
423
+ // Each measured chore's own artefact reaches its own prompt: the regression this whole test exists for.
424
424
  const promptFor = (chore: string) => due.find((verdict) => verdict.chore.id === chore)?.prompt ?? ``;
425
425
  expect(promptFor(`security-advisories`)).toContain(`left-pad`);
426
426
  expect(promptFor(`dependencies-outdated`)).toContain(`vue 1.0.0 → 2.0.0`);
@@ -432,7 +432,7 @@ describe(`the prompts`, () => {
432
432
  });
433
433
  });
434
434
 
435
- /* APPLICABILITY whether the chore is a QUESTION worth asking of this repository, as opposed to whether the
435
+ /* APPLICABILITY, whether the chore is a QUESTION worth asking of this repository, as opposed to whether the
436
436
  * answer is yes. Every case here is one where the previous design showed a row that could never be acted on:
437
437
  * an offer to re-read documentation that was never written, to slim an image that does not exist, to tighten a
438
438
  * pipeline nobody has. Each of those teaches the reader that this list was not written by someone who looked. */
@@ -468,7 +468,7 @@ describe(`what does not apply here`, () => {
468
468
  expect(verdictFor(single, `library-overlap`).state).toBe(`not-applicable`);
469
469
  });
470
470
 
471
- // A survey has no evidence to be absent "90 days have passed" is true everywhere so without a gate it
471
+ // A survey has no evidence to be absent: "90 days have passed" is true everywhere, so without a gate it
472
472
  // fires forever in repositories where its subject does not exist. This is the regression that motivated
473
473
  // making `applies` a required field on SurveySpec rather than an optional one.
474
474
  test(`a tiny repository is not surveyed for cross-cutting patterns it cannot have`, () => {
@@ -498,7 +498,7 @@ describe(`what does not apply here`, () => {
498
498
 
499
499
  /* THE CAUSES HAVE TO GROUP, and that is a fact about the STRINGS rather than about the gates. The scope strip
500
500
  * prints one line per distinct cause with the chores it costs listed beside it, so two gates that both mean
501
- * "there is no package.json here" and say it in different words print two lines and a workspace root, where
501
+ * "there is no package.json here" and say it in different words print two lines, and a workspace root, where
502
502
  * a dozen chores are ruled out by three facts, is back to the paragraph-per-chore wall this phrasing replaced.
503
503
  * Bounded rather than enumerated: a new gate may invent a new cause, it may not invent a new sentence. */
504
504
  test(`applicability reasons are bare causes, so the ones that mean the same thing group`, () => {
@@ -515,7 +515,7 @@ describe(`what does not apply here`, () => {
515
515
  });
516
516
  });
517
517
 
518
- /* THE FRONT-END CHORES. Four chores over two probes, tested where they decide something the share that makes a
518
+ /* THE FRONT-END CHORES. Four chores over two probes, tested where they decide something: the share that makes a
519
519
  * bundle a finding, the names that make two components one component, and above all the digests, because three of
520
520
  * these four measure things that move every time anyone writes a line of markup. */
521
521
  const uiProbe = (scan: Partial<UiScan> = {}): ProbeResult =>
@@ -601,8 +601,8 @@ describe(`idioms the framework has replaced`, () => {
601
601
  expect(verdict.detail).toEqual([`3 files · the Options API → <script setup> with the Composition API`]);
602
602
  });
603
603
 
604
- /* A migration in progress is a set that changes on every commit, so digesting the file identities which is
605
- * right for the documentation chore, whose set is packages would badge continuously through exactly the
604
+ /* A migration in progress is a set that changes on every commit, so digesting the file identities, which is
605
+ * right for the documentation chore, whose set is packages: would badge continuously through exactly the
606
606
  * period someone is doing the work. The bucketed count moves on real progress and not on daily churn. */
607
607
  test(`one more file in a large migration is not news`, () => {
608
608
  const before = verdictFor(withProbes([uiProbe({ idioms: [idioms(`vue-options-api`, 40)] })]), `framework-idiom`);
@@ -668,7 +668,7 @@ describe(`components built twice`, () => {
668
668
  });
669
669
 
670
670
  /* Half a measurement would let the row claim it looked for shared logic in a repository where jscpd has never
671
- * run the "measured and found nothing" lie the unavailable state exists to prevent. */
671
+ * run: the "measured and found nothing" lie the unavailable state exists to prevent. */
672
672
  test(`without the clone sweep the chore is unavailable, not clear`, () => {
673
673
  expect(verdictFor(withProbes([components(`src/Button.vue`, `src/ui/Button.vue`)]), `component-overlap`).state).toBe(`unavailable`);
674
674
  });
@@ -692,7 +692,7 @@ describe(`hard-coded styles`, () => {
692
692
  expect(verdict.detail[0]).toBe(`src/Checkout.vue · 11 values`);
693
693
  });
694
694
 
695
- // Tailwind gates this one alone a Vue repository with no Tailwind has no theme scale to have bypassed, and
695
+ // Tailwind gates this one alone: a Vue repository with no Tailwind has no theme scale to have bypassed, and
696
696
  // a row saying so would be the surface inventing a subject.
697
697
  test(`a repository without Tailwind is not asked the question at all`, () => {
698
698
  const verdict = verdictFor(
@@ -713,7 +713,7 @@ describe(`hard-coded styles`, () => {
713
713
  });
714
714
  });
715
715
 
716
- /* THE CRITERION the rule in words, next to the evidence that met it. A row that reports a number without the
716
+ /* THE CRITERION: the rule in words, next to the evidence that met it. A row that reports a number without the
717
717
  * rule behind it is asking to be taken on trust, and the first row that turns out to be wrong costs the whole
718
718
  * list its credibility. */
719
719
  describe(`every chore says what would make it due`, () => {
@@ -2,18 +2,18 @@ import type { ChoreLedgerEntry, ChoresReport, ProbeId, ProbeResult } from "../sc
2
2
  import { type Chore, type ChoreContext, type ChoreFinding, CHORES, chorePrompt } from "./chores.js";
3
3
  import { probeSpec } from "./probes.js";
4
4
 
5
- /* FROM EVIDENCE TO A VERDICT the one place that decides whether a chore is due, and the only place that is
5
+ /* FROM EVIDENCE TO A VERDICT, the one place that decides whether a chore is due, and the only place that is
6
6
  * allowed to. Both the Maintenance panel and its rail badge run this function over the same report, so the number
7
7
  * on the tile and the reason in the panel are the same computation and cannot drift apart.
8
8
  *
9
9
  * Six states, and the distinctions between them are the whole design:
10
10
  *
11
- * not-applicable this chore is not a QUESTION worth asking of this repository there is no Dockerfile to
11
+ * not-applicable this chore is not a QUESTION worth asking of this repository, there is no Dockerfile to
12
12
  * slim, no pipeline to tighten, no documentation to re-read. Dropped from the panel entirely
13
13
  * rather than shown as clear, because "clear" claims we checked, and there was nothing to check.
14
14
  * The reason survives in the scope strip, so "why is there no Docker chore here?" has an answer.
15
15
  * unavailable we have not measured this. knip is not a devDependency; there is no lockfile to audit. Rendered
16
- * greyed, never badged, and never collapsed into `clear` a maintenance surface reporting a green
16
+ * greyed, never badged, and never collapsed into `clear`, a maintenance surface reporting a green
17
17
  * repository it has never actually measured is worse than one that says nothing.
18
18
  * clear we measured, and there is nothing to do. This is the common state, and it has to be visibly
19
19
  * reachable or the panel is just a list of complaints.
@@ -23,23 +23,23 @@ import { probeSpec } from "./probes.js";
23
23
  * due there is something to do.
24
24
  *
25
25
  * The first three are all ways of saying "no", and keeping them apart is what makes the surface trustworthy: they
26
- * mean we cannot ask, we did not measure, and we measured and found nothing three different claims, and only
26
+ * mean we cannot ask, we did not measure, and we measured and found nothing, three different claims, and only
27
27
  * the last one is reassurance.
28
28
  *
29
29
  * And one flag that is not a state: `settled`. A due chore that has been RE-MEASURED since a turn was spent on
30
- * it, and whose evidence did not move, stays due because it is but must never light the rail again. This is
30
+ * it, and whose evidence did not move, stays due, because it is, but must never light the rail again. This is
31
31
  * what stops the surface repeating itself while a fix sits in review, and it is why the ledger stores a digest
32
32
  * rather than a timestamp: "ran 3 days ago" cannot tell you whether it ran against THIS.
33
33
  *
34
34
  * `stale` is the other half of that sentence, and it exists because the digest alone cannot tell the two apart.
35
35
  * A probe that never re-ran produces an unchanged digest for free, so "the fix did not move the numbers" and "we
36
- * have not looked since the fix" arrived at this function looking identical and the panel showed the second as
36
+ * have not looked since the fix" arrived at this function looking identical, and the panel showed the second as
37
37
  * the first, quoting a week-old count an hour after the work that invalidated it. Comparing the run's time to the
38
38
  * MEASUREMENT's time is what separates them, and it is a comparison of two numbers the report already carries.
39
39
  *
40
40
  * Nothing here can hide a problem. Snoozing and settling change whether the rail SPEAKS; the panel still shows
41
41
  * the chore, its evidence and its state. The one thing that removes a row entirely is `not-applicable`, and that
42
- * is not hiding it is the absence of a subject, counted in the panel's scope strip and expandable to the reason.
42
+ * is not hiding, it is the absence of a subject, counted in the panel's scope strip and expandable to the reason.
43
43
  * A maintenance surface you can quietly bury findings in is a maintenance surface nobody trusts. */
44
44
 
45
45
  export type ChoreState = "due" | "clear" | "snoozed" | "stale" | "unavailable" | "not-applicable";
@@ -49,16 +49,16 @@ export interface ChoreVerdict {
49
49
  readonly repo: string;
50
50
  readonly state: ChoreState;
51
51
  readonly severity: ChoreFinding["severity"];
52
- // Always present, in every state "nothing to do" and "not measured" are answers a reader deserves in words.
52
+ // Always present, in every state, "nothing to do" and "not measured" are answers a reader deserves in words.
53
53
  readonly headline: string;
54
54
  readonly detail: readonly string[];
55
55
  // The evidence identity. Empty for `unavailable`, where there is no evidence to identify.
56
56
  readonly digest: string;
57
- /* WHEN THE EVIDENCE WAS TAKEN the fact every row shows beside its numbers, and the one whose absence let a
57
+ /* WHEN THE EVIDENCE WAS TAKEN, the fact every row shows beside its numbers, and the one whose absence let a
58
58
  * measurement from last Tuesday read as this morning's. Undefined when the verdict rests on no measurement at
59
59
  * all: a survey is decided by the calendar, and an unavailable chore has nothing to be out of date. */
60
60
  readonly measuredAt: number | undefined;
61
- // The turn. Present only when there is something to do a "start an agent" button on a clear chore is an
61
+ // The turn. Present only when there is something to do, a "start an agent" button on a clear chore is an
62
62
  // invitation to spend money proving that nothing is wrong.
63
63
  readonly prompt: string | undefined;
64
64
  readonly lastRun: ChoreLedgerEntry | undefined;
@@ -68,7 +68,7 @@ export interface ChoreVerdict {
68
68
  }
69
69
 
70
70
  /* HOW OLD THE EVIDENCE IS: the OLDEST of the measurements a verdict rests on, because a claim is only as current
71
- * as the least current thing it was computed from. Undefined when it rests on none a survey has no measurement,
71
+ * as the least current thing it was computed from. Undefined when it rests on none, a survey has no measurement,
72
72
  * and an unavailable chore's probe did not produce one. */
73
73
  const measurementAge = (needs: readonly ProbeId[], probes: ReadonlyMap<ProbeId, ProbeResult>): number | undefined => {
74
74
  const taken = needs.flatMap((id) => {
@@ -79,7 +79,7 @@ const measurementAge = (needs: readonly ProbeId[], probes: ReadonlyMap<ProbeId,
79
79
  };
80
80
 
81
81
  // A survey that is clear is clear because it was READ recently, and saying so is the only way its row means
82
- // anything "nothing to do" under a chore that has no measurement would be a claim about the code rather than
82
+ // anything, "nothing to do" under a chore that has no measurement would be a claim about the code rather than
83
83
  // about the calendar.
84
84
  const clearHeadline = (chore: Chore, lastRun: ChoreLedgerEntry | undefined, nowMs: number): string =>
85
85
  chore.survey === true && lastRun !== undefined ? `Surveyed ${Math.round((nowMs - lastRun.ranAt) / 86_400_000)} days ago` : `Nothing to do`;
@@ -102,16 +102,16 @@ const unmeasuredDetail = (needs: readonly ProbeId[], probes: ReadonlyMap<ProbeId
102
102
  if (probe.state === `unavailable`) {
103
103
  return [`${spec.title} · ${probe.reason ?? `not available in this repository`}`];
104
104
  }
105
- return [`${spec.title} · failed${probe.reason === undefined ? `` : ` ${probe.reason}`}`];
105
+ return [`${spec.title} · failed${probe.reason === undefined ? `` : `, ${probe.reason}`}`];
106
106
  });
107
107
 
108
108
  export const assessChore = (chore: Chore, context: ChoreContext, ledger: ChoreLedgerEntry | undefined): ChoreVerdict => {
109
109
  const base = { chore, repo: context.repo, lastRun: ledger, settled: false, prompt: undefined } as const;
110
110
 
111
111
  /* APPLICABILITY FIRST, before anything is measured or any evidence is read. A chore that does not apply is
112
- * not "clear" and not "unmeasured" the question does not arise here, and every subsequent branch of this
113
- * function would be answering it anyway. The cause is carried as the headline, because the scope strip which
114
- * groups these rows BY it is the only place it will ever be read. */
112
+ * not "clear" and not "unmeasured", the question does not arise here, and every subsequent branch of this
113
+ * function would be answering it anyway. The cause is carried as the headline, because the scope strip, which
114
+ * groups these rows BY it, is the only place it will ever be read. */
115
115
  const inapplicable = chore.applies?.(context.signals);
116
116
  if (inapplicable !== undefined) {
117
117
  return { ...base, state: `not-applicable`, severity: `info`, headline: inapplicable, detail: [], digest: ``, measuredAt: undefined };
@@ -138,7 +138,7 @@ export const assessChore = (chore: Chore, context: ChoreContext, ledger: ChoreLe
138
138
  };
139
139
  }
140
140
 
141
- /* Has the last run's settlement lapsed? A cadence of 0 means "this is decided by evidence alone" an advisory
141
+ /* Has the last run's settlement lapsed? A cadence of 0 means "this is decided by evidence alone", an advisory
142
142
  * does not become worth looking at again because ninety days passed, it becomes worth looking at again when
143
143
  * the advisory set changes. Anything with a cadence expires its own settlement, so "we looked and chose not to
144
144
  * act" cannot silence a chore for good. */
@@ -176,7 +176,7 @@ export const assessChore = (chore: Chore, context: ChoreContext, ledger: ChoreLe
176
176
  };
177
177
  }
178
178
 
179
- /* The agent looked at exactly this evidence and reported that there was nothing in it knip's findings were
179
+ /* The agent looked at exactly this evidence and reported that there was nothing in it, knip's findings were
180
180
  * all public entry points, the clones were all generated files. That verdict has to stick, or the next poll
181
181
  * starts the same turn again and the surface has taught the owner that its rows are wrong. It stops sticking
182
182
  * when the evidence changes (a different digest) or the cadence lapses. */
@@ -185,7 +185,7 @@ export const assessChore = (chore: Chore, context: ChoreContext, ledger: ChoreLe
185
185
  ...base,
186
186
  state: `clear`,
187
187
  severity: `info`,
188
- headline: `Checked the findings did not hold up`,
188
+ headline: `Checked, the findings did not hold up`,
189
189
  detail: finding.detail,
190
190
  digest: finding.digest,
191
191
  measuredAt,
@@ -193,10 +193,10 @@ export const assessChore = (chore: Chore, context: ChoreContext, ledger: ChoreLe
193
193
  }
194
194
 
195
195
  /* THE MEASUREMENT IS OLDER THAN THE WORK. A turn landed after the last time we looked, so the evidence below
196
- * describes a tree that no longer exists an hour after a run deleted the dead code, the row was still
196
+ * describes a tree that no longer exists, an hour after a run deleted the dead code, the row was still
197
197
  * quoting the count from six days before it. `sameEvidence` cannot catch this: an unchanged digest is exactly
198
198
  * what a probe that never re-ran produces, so the flag says "settled" at its most confident when it knows
199
- * least. The chore steps down instead evidence stays on the row, the CLAIM comes off it and carries no
199
+ * least. The chore steps down instead, evidence stays on the row, the CLAIM comes off it, and carries no
200
200
  * prompt, because the honest next move is to measure again rather than to spend a second turn on a finding
201
201
  * nobody has re-checked. It cannot badge either, which is what stops the tile lighting for work already done. */
202
202
  if (ledger !== undefined && measuredAt !== undefined && ledger.ranAt > measuredAt) {
@@ -220,8 +220,8 @@ export const assessChore = (chore: Chore, context: ChoreContext, ledger: ChoreLe
220
220
  // two independent questions with two independent answers.
221
221
  export const ledgerKey = (repo: string, chore: string): string => `${repo}|${chore}`;
222
222
 
223
- /* Every chore in every repo, from one report. This is what both surfaces call the panel groups the result, the
224
- * badge filters it so there is exactly one traversal of the book in the codebase and adding a chore to CHORES
223
+ /* Every chore in every repo, from one report. This is what both surfaces call, the panel groups the result, the
224
+ * badge filters it, so there is exactly one traversal of the book in the codebase and adding a chore to CHORES
225
225
  * reaches both surfaces without touching either. */
226
226
  export const assessReport = (report: ChoresReport, nowMs: number): ChoreVerdict[] => {
227
227
  const ledger = new Map(report.ledger.map((entry) => [ledgerKey(entry.repo, entry.chore), entry]));
@@ -232,10 +232,10 @@ export const assessReport = (report: ChoresReport, nowMs: number): ChoreVerdict[
232
232
  };
233
233
 
234
234
  /* WHAT THE RAIL IS ALLOWED TO SAY. A badge must mean "something happened here that you don't already know about",
235
- * never "here is a statistic" the extension API states that bar and this is the function that holds this
235
+ * never "here is a statistic", the extension API states that bar and this is the function that holds this
236
236
  * surface to it. Three filters, and every one of them removes a case that would otherwise light the tile forever:
237
237
  *
238
- * state === due the obvious one and it is also what keeps `stale` silent, since a measurement taken before
238
+ * state === due the obvious one, and it is also what keeps `stale` silent, since a measurement taken before
239
239
  * the last turn is not a fact anyone should be interrupted about.
240
240
  * !settled a turn has been spent on this chore and the re-measured evidence did not move.
241
241
  * unseen digest the owner has already LOOKED at this evidence in the panel. Acknowledgement is per digest
@@ -2,7 +2,7 @@ import { readFileSync } from "node:fs";
2
2
  import { expect, test } from "vitest";
3
3
  import { currentLock } from "./contract-lock.js";
4
4
 
5
- /* The committed lock and the code must say the same thing see contract-lock.ts for what the pair buys.
5
+ /* The committed lock and the code must say the same thing: see contract-lock.ts for what the pair buys.
6
6
  *
7
7
  * This is the HALF that runs everywhere the tests run; the other half (a shrunk lock needs a declared break)
8
8
  * lives in prepass.mjs, which has git and this suite does not. */
@@ -1,24 +1,24 @@
1
- /* THE WIRE SURFACE, AS ONE COMPARABLE DOCUMENT every zod schema this package exports, serialized to JSON
1
+ /* THE WIRE SURFACE, AS ONE COMPARABLE DOCUMENT, every zod schema this package exports, serialized to JSON
2
2
  * Schema and sorted into a canonical shape. `contract.lock.json` at the package root is this function's
3
3
  * committed output, and the pair is what makes "did the contract change?" a question git can answer.
4
4
  *
5
5
  * Two readers depend on that answer. contract-lock.test.ts fails whenever the committed file is out of step
6
- * with the code, so a contract change always lands WITH its lock diff visible in review, attributable to a
6
+ * with the code, so a contract change always lands WITH its lock diff, visible in review, attributable to a
7
7
  * commit. And prepass.mjs (invariant 6) compares the committed lock against its merge-base to insist that a
8
- * SHRUNK surface a schema or field removed, a type changed arrives as a `!` commit carrying a
8
+ * SHRUNK surface, a schema or field removed, a type changed, arrives as a `!` commit carrying a
9
9
  * `Breaking-Note:` trailer, which is what feeds the release's "Breaking changes" section and the update card's
10
10
  * warning. Additions pass freely; every persisted-manifest reader parses loosely, so growth breaks nobody.
11
11
  *
12
12
  * Serialized from the package's EXPORTS rather than a hand-kept list, on the repo's own rule (AGENTS.md:
13
13
  * "guard invariants by discovery, not enumeration"): a schema added tomorrow is in the lock tomorrow, and a
14
14
  * schema that stops being exported is a removal the lock shows. `unrepresentable: "any"` keeps the rare
15
- * function-valued corner from throwing it serializes as `{}`, which still diffs when it moves. */
15
+ * function-valued corner from throwing, it serializes as `{}`, which still diffs when it moves. */
16
16
 
17
17
  import { z } from "zod";
18
18
  import * as contract from "./index.js";
19
19
 
20
20
  // Canonical ordering, so two runs of the same code are byte-identical and a lock diff is a contract diff
21
- // rather than a key-order shuffle. Arrays keep their order for `required` and `enum` lists the order zod
21
+ // rather than a key-order shuffle. Arrays keep their order, for `required` and `enum` lists the order zod
22
22
  // emits is stable, and sorting them would hide a reorder that is genuinely no change at all anyway.
23
23
  const sorted = (value: unknown): unknown => {
24
24
  if (Array.isArray(value)) {
@@ -43,7 +43,7 @@ export const currentLock = (): Record<string, unknown> => {
43
43
  }
44
44
  try {
45
45
  const schema = sorted(z.toJSONSchema(value, { unrepresentable: "any" })) as Record<string, unknown>;
46
- // The dialect banner is identical on all ~500 entries pure weight, no surface.
46
+ // The dialect banner is identical on all ~500 entries, pure weight, no surface.
47
47
  delete schema["$schema"];
48
48
  lock[name] = schema;
49
49
  } catch {
@@ -55,7 +55,7 @@ export const currentLock = (): Record<string, unknown> => {
55
55
  return lock;
56
56
  };
57
57
 
58
- /* ONE LINE PER SCHEMA, on purpose not JSON.stringify(lock, null, 4). Pretty-printed, the lock is a 35k-line
58
+ /* ONE LINE PER SCHEMA, on purpose, not JSON.stringify(lock, null, 4). Pretty-printed, the lock is a 35k-line
59
59
  * wall nobody scrolls; a line per export keeps it ~500 lines and makes `git diff` read as the list of WHICH
60
60
  * surfaces moved, which is the level a reviewer reviews at. The path-level detail lives in the tooling: the
61
61
  * lock test diffs parsed objects and prepass invariant 6 names the exact removed paths, so nothing is lost by
@@ -1,7 +1,7 @@
1
1
  import { oc } from "@orpc/contract";
2
2
  import { ActivityListSchema, ActivityQuerySchema, ActivityStatusSchema } from "../schemas.js";
3
3
 
4
- // The activity audit feed (provider-agnostic; Discord is the first source). Read-only by design
4
+ // The activity audit feed (provider-agnostic; Discord is the first source). Read-only by design,
5
5
  // events are appended daemon-side only, so the log stays a trustworthy record of what the agent did.
6
6
  export const activityContract = {
7
7
  list: oc.route({ method: "GET", path: "/activity" }).input(ActivityQuerySchema).output(ActivityListSchema),
@@ -14,7 +14,7 @@ import {
14
14
  } from "../schemas.js";
15
15
 
16
16
  // A turn EXECUTES as a detached daemon-side run: `run` starts it and acks with the run id; any number of
17
- // clients render it via `attach` (replay from a seq cursor, then live) the initiating window holds no
17
+ // clients render it via `attach` (replay from a seq cursor, then live), the initiating window holds no
18
18
  // special stream, so a reload, a second window, or another device attaches identically. `reply` un-parks a
19
19
  // turn waiting on any interactive card (plan approval, clarifying questions, a per-tool permission prompt);
20
20
  // steer injects a user message into the running turn; stop hard-cancels it daemon-side.
@@ -25,7 +25,7 @@ export const agentContract = {
25
25
  steer: oc.route({ method: "POST", path: "/agent/steer" }).input(SteerSchema).output(OkSchema),
26
26
  stop: oc.route({ method: "POST", path: "/agent/stop" }).input(StopTurnSchema).output(OkSchema),
27
27
  // Go back to a message: restore the workspace to that turn's checkpoint, drop the messages after it, and
28
- // forget the provider session. CONFLICT while a turn is running a restore cannot overwrite files an
28
+ // forget the provider session. CONFLICT while a turn is running, a restore cannot overwrite files an
29
29
  // agent is editing. NOT_FOUND when that message has no checkpoint to go back to.
30
30
  rewind: oc.route({ method: "POST", path: "/agent/rewind" }).input(RewindTurnSchema).output(RewindResultSchema),
31
31
  // The provider's slash commands as last published by one of its turns, so a conversation's `/` popover is
@@ -24,14 +24,14 @@ import {
24
24
 
25
25
  // The fleet: every registered conversation-agent (see AgentSummarySchema). Registry-level actions (read,
26
26
  // rename, seen, archive) apply uniformly. For branch-backed conversations, `diff` is the worktree's CUMULATIVE
27
- // multi-repo delta vs its recorded per-repo bases one flat change set per repo
27
+ // multi-repo delta vs its recorded per-repo bases, one flat change set per repo
28
28
  // (AgentChanges), each file flagged `landed` or not, deliberately not the working tree's staged/unstaged
29
29
  // shape: a worktree the user never checks out has no index they could stage into. `land` merges the worktree
30
30
  // branches into the main tree (per-repo, conflicts reported, nothing lost on failure); `discard` removes
31
31
  // worktrees + branches + registry entry. Those branch actions reject workspace conversations explicitly. An
32
32
  // unknown {id} is NOT_FOUND; land/discard while the turn runs is CONFLICT.
33
- // `rename` sets the user-chosen display title legal mid-turn (it touches no worktree state).
34
- // `seen`/`seenAll` stamp the read marker behind the cards' unread badge (AgentSummarySchema.seenAt) also
33
+ // `rename` sets the user-chosen display title, legal mid-turn (it touches no worktree state).
34
+ // `seen`/`seenAll` stamp the read marker behind the cards' unread badge (AgentSummarySchema.seenAt), also
35
35
  // legal mid-turn, and like `rename` they never bump `updatedAt` (reading is not activity).
36
36
  //
37
37
  // ARCHIVE is the non-destructive counterpart to discard, and the one the board leans on: for an isolated
@@ -41,7 +41,7 @@ import {
41
41
  // surviving branch. Archiving a running agent is CONFLICT, same as land/discard.
42
42
  //
43
43
  // PURGE empties the archive, and it is `discard` applied to every agent already in there: worktree remnants,
44
- // branches and entries all go. Deliberately the whole archive and not a list of ids the archive is the pile
44
+ // branches and entries all go. Deliberately the whole archive and not a list of ids, the archive is the pile
45
45
  // of agents the user has already decided are over, so "clean it up" is one act with one confirmation, and a
46
46
  // per-id purge would be `discard`, which already exists. Never touches a running agent (a turn un-archives its
47
47
  // own agent, so there should be none) and answers with what it actually deleted: a teardown that fails on one
@@ -49,31 +49,31 @@ import {
49
49
  export const agentsContract = {
50
50
  list: oc.route({ method: "GET", path: "/agents" }).output(AgentsListSchema),
51
51
  archived: oc.route({ method: "GET", path: "/agents/archived" }).output(AgentsListSchema),
52
- // The board's filter. Answers over BOTH halves of the fleet the live roster and the archive because
52
+ // The board's filter. Answers over BOTH halves of the fleet, the live roster and the archive, because
53
53
  // the board hides by design (the Finished lane windows to a handful, archived agents are off the roster
54
54
  // entirely), and a filter that reports "no matches" while the agent sits one click away is a lie. The
55
55
  // never-carded conversations that are neither are `sessions.list`'s query, which matches by the same rule.
56
56
  search: oc.route({ method: "GET", path: "/agents/search" }).input(AgentSearchQuerySchema).output(AgentSearchResultSchema),
57
57
  get: oc.route({ method: "GET", path: "/agents/{id}" }).input(AgentIdSchema).output(AgentSummarySchema),
58
58
  transcript: oc.route({ method: "GET", path: "/agents/{id}/transcript" }).input(AgentIdSchema).output(AgentTranscriptSchema),
59
- /* SPEAK AS THE AGENT append the user's words to the conversation's record as an assistant row, with no
59
+ /* SPEAK AS THE AGENT, append the user's words to the conversation's record as an assistant row, with no
60
60
  * turn behind them and no reply. The row is marked `placed` for human readers (RestoredMessageSchema); the
61
61
  * provider session is FORGOTTEN in the same breath, rewind-style, so the next real turn opens a fresh
62
- * runtime session seeded from the record where the placed line reads as the agent's own words, because
62
+ * runtime session seeded from the record, where the placed line reads as the agent's own words, because
63
63
  * the handoff renders every assistant row identically. A running turn is CONFLICT: the illusion can only be
64
64
  * established between turns, and a concurrent turn would resume the very session this exists to retire. */
65
65
  place: oc.route({ method: "POST", path: "/agents/{id}/place" }).input(AgentPlaceSchema).output(OkSchema),
66
66
  rename: oc.route({ method: "POST", path: "/agents/{id}/rename" }).input(AgentRenameSchema).output(AgentSummarySchema),
67
- // This agent's own land-at-completion posture an override of the sandbox-wide `autoLand` setting; null
67
+ // This agent's own land-at-completion posture, an override of the sandbox-wide `autoLand` setting; null
68
68
  // clears it back to "inherit". Legal mid-turn on purpose: the setting is read at turn COMPLETION, so
69
69
  // flipping it while the agent works is exactly "hold THIS turn's work for review", the press that matters.
70
70
  autoLand: oc.route({ method: "POST", path: "/agents/{id}/auto-land" }).input(AgentAutoLandSchema).output(AgentSummarySchema),
71
- /* THIS conversation's answer to a provider outage an override of the sandbox-wide `resumeAfterOutage`
71
+ /* THIS conversation's answer to a provider outage, an override of the sandbox-wide `resumeAfterOutage`
72
72
  * setting; null clears it back to "inherit". The chat's offer at the moment a turn dies writes this and
73
73
  * never the global: the press happens inside one conversation and means "finish this piece of work", so
74
- * its honest blast radius is that conversation. Sandbox Agent owns the default for everything else.
74
+ * its honest scope is that conversation. Sandbox > Agent owns the default for everything else.
75
75
  *
76
- * Legal mid-turn, and unlike autoLand it is legal for a WORKSPACE conversation too an outage kills a
76
+ * Legal mid-turn, and unlike autoLand it is legal for a WORKSPACE conversation too, an outage kills a
77
77
  * main-tree chat exactly as readily as an isolated one, and there is no branch involved either way. */
78
78
  resumeAfterOutage: oc
79
79
  .route({ method: "POST", path: "/agents/{id}/resume-after-outage" })
@@ -11,14 +11,14 @@ import {
11
11
  } from "../schemas.js";
12
12
 
13
13
  // The sandbox's automations manifest (scheduled agent wake-ups). `list` returns each automation with its recent
14
- // runs + next fire time. `upsert` adds or edits by id (nothing to provision the scheduler picks it up on its
14
+ // runs + next fire time. `upsert` adds or edits by id (nothing to provision, the scheduler picks it up on its
15
15
  // next poll); `setEnabled` changes only the switch, so a list-row action never has to reconstruct the record.
16
16
  // `remove` deletes.
17
17
  // The `pending*` routes are the owner's approval queue: a `requireApproval` automation holds each fire here
18
18
  // instead of waking; `approve` runs the held wake, `reject` drops it.
19
19
  export const automationsContract = {
20
20
  list: oc.route({ method: "GET", path: "/automations" }).output(AutomationsListSchema),
21
- /* WHAT CAN WAKE AN AGENT HERE, and what to start from the daemon's own sources and templates merged with
21
+ /* WHAT CAN WAKE AN AGENT HERE, and what to start from, the daemon's own sources and templates merged with
22
22
  * every installed extension's. The composer's entire vocabulary, so that adding a trigger to an area is a
23
23
  * change to that area and to nothing else. `upsert` below validates against the same merge, which is what
24
24
  * keeps the surface and the daemon from disagreeing about what is allowed. */
@@ -26,18 +26,18 @@ export const automationsContract = {
26
26
  upsert: oc.route({ method: "POST", path: "/automations" }).input(AutomationSchema).output(OkSchema),
27
27
  setEnabled: oc.route({ method: "POST", path: "/automations/{id}/enabled" }).input(AutomationEnabledInputSchema).output(OkSchema),
28
28
  remove: oc.route({ method: "DELETE", path: "/automations/{id}" }).input(AutomationIdParamSchema).output(OkSchema),
29
- /* Fire one automation NOW, by hand the answer to "I wrote a 3 a.m. cron and I have no way to try it".
29
+ /* Fire one automation NOW, by hand, the answer to "I wrote a 3 a.m. cron and I have no way to try it".
30
30
  * It runs the SAME path the real trigger runs: a schedule stays a headless main-tree wake, because a
31
31
  * test-fire that proves an isolated worktree works proves nothing about the fire it is standing in for. The
32
32
  * guard runs too ("skipped by guard" is the most useful thing this can report); only the approval gate is
33
33
  * skipped, since pressing the button IS the owner's approval.
34
34
  *
35
- * Owner-explicit, so a DISABLED automation fires as well trying a prompt before switching it on is the
35
+ * Owner-explicit, so a DISABLED automation fires as well, trying a prompt before switching it on is the
36
36
  * main reason to press this, and unlike the /automations/{id}/fire webhook there is no outside sender here to
37
37
  * fail closed against.
38
38
  *
39
39
  * NOT FOR A LISTENER, which is the one trigger whose fire is nothing without the thing that fired it. A
40
- * listener's prompt is a brief about handling the events riding with it, and by hand there are none so the
40
+ * listener's prompt is a brief about handling the events riding with it, and by hand there are none, so the
41
41
  * button could only ever produce an agent told to handle events, handed none, asking where they went. Worse,
42
42
  * that pointless run took the automation's turn: a real mention arriving while it ran had to wait behind it.
43
43
  * Refused here rather than hidden in the UI alone, because the honest answer to "how do I test this" is to
@@ -19,14 +19,14 @@ import {
19
19
  /* The sandbox's unified capability manifest. `list` returns each active capability with its live status, the
20
20
  * non-secret echo of its config, and the NAMES of the credentials it holds. `add` upserts a capability and
21
21
  * STREAMS its apply (devops scaffolding / service provisioning emit ndjson progress; mcp/integration emit a
22
- * terminal frame), mirroring the /intentic runner. `remove` tears it down (devops refuses deleting the repos
22
+ * terminal frame), mirroring the /intentic runner. `remove` tears it down (devops refuses, deleting the repos
23
23
  * is data loss). `status` re-probes a single capability for a lazy UI refresh. `marketplace` resolves a Claude
24
24
  * Code plugin marketplace repo into installable plugin-capability configs.
25
25
  *
26
26
  * `add` IS ALSO THE EDIT, because the write is an upsert: the same id with a changed config changes that
27
27
  * connection. A caller editing one has never been shown its credentials, so it sends VAULTED
28
28
  * (capability-secrets.ts) for each it is leaving alone and the daemon resolves those from what is stored before
29
- * anything runs the only way to change one setting on a tunnel without re-typing its key. A marker with
29
+ * anything runs, the only way to change one setting on a tunnel without re-typing its key. A marker with
30
30
  * nothing behind it is refused rather than written. */
31
31
  export const capabilitiesContract = {
32
32
  list: oc.route({ method: "GET", path: "/capabilities" }).output(CapabilitiesListSchema),
@@ -34,24 +34,24 @@ export const capabilitiesContract = {
34
34
  remove: oc.route({ method: "DELETE", path: "/capabilities/{id}" }).input(CapabilityIdParamSchema).output(OkSchema),
35
35
  /* Give a connection a different name, carrying what the old one keyed: a browser profile with its logins,
36
36
  * a connected machine's enrollment, an extension's checkout. The name is the agent's handle for the thing,
37
- * so this is a migration add + remove would lose exactly the state that makes the connection worth
37
+ * so this is a migration, add + remove would lose exactly the state that makes the connection worth
38
38
  * keeping. A kind whose name is part of what it IS (the scaffolders, the one-per-sandbox cards) refuses. */
39
39
  rename: oc.route({ method: "POST", path: "/capabilities/{id}/rename" }).input(CapabilityRenameSchema).output(OkSchema),
40
40
  // Replace just the secret in a capability's config (the /secrets page's edit) and re-run its apply.
41
41
  setSecret: oc.route({ method: "POST", path: "/capabilities/{id}/secret" }).input(CapabilitySecretInputSchema).output(OkSchema),
42
42
  status: oc.route({ method: "GET", path: "/capabilities/{id}/status" }).input(CapabilityIdParamSchema).output(CapabilityStatusSchema),
43
- /* One capability's stored config, secrets included how an extension BACKEND dials the service behind a
43
+ /* One capability's stored config, secrets included, how an extension BACKEND dials the service behind a
44
44
  * connected capability (ext-deployments reads its Komodo's key pair through this). Never a browser's: the
45
45
  * handler refuses any caller with a member identity, so only the daemon's header grants reach it, and an
46
- * extension's grant reaches it only when its manifest declares the route in `permissions.daemon` which
46
+ * extension's grant reaches it only when its manifest declares the route in `permissions.daemon`, which
47
47
  * is the install dialog saying, in one line, "this extension can read connected credentials". */
48
48
  connection: oc.route({ method: "GET", path: "/capabilities/{id}/connection" }).input(CapabilityIdParamSchema).output(CapabilityConnectionSchema),
49
49
  marketplace: oc.route({ method: "POST", path: "/capabilities/marketplace" }).input(MarketplaceRequestSchema).output(MarketplaceSchema),
50
50
  // "Not needed": stop offering this card until the workspace evidence behind it changes. Nothing is torn
51
- // down and nothing is remembered about the card itself only the evidence it was declined against.
51
+ // down and nothing is remembered about the card itself, only the evidence it was declined against.
52
52
  dismiss: oc.route({ method: "DELETE", path: "/capabilities/recommendations/{card}" }).input(CapabilityCardParamSchema).output(OkSchema),
53
53
  // Start an agent-kind capability's interactive login (its declared loginCommand) in a visible terminal
54
- // session the user types into device-code sign-in flows. Returns the session the panel attaches to.
54
+ // session the user types into, device-code sign-in flows. Returns the session the panel attaches to.
55
55
  login: oc.route({ method: "POST", path: "/capabilities/{id}/login" }).input(CapabilityIdParamSchema).output(CapabilityLoginSchema),
56
56
  // Mint one TOTP code from the capability's stored seed (a field its card marks `totp`). The one capability
57
57
  // read the agent token is admitted to (see auth/grants): a code expires within its period and never reveals
@@ -2,19 +2,19 @@ import { oc } from "@orpc/contract";
2
2
  import { ChoreLedgerWriteSchema, ChoreProbeRequestSchema, ChoresReportSchema, OkSchema } from "../schemas.js";
3
3
 
4
4
  /* Maintenance evidence: what every repo under /work currently measures, and what has already been done about it.
5
- * Three routes, because there are exactly three things the surface does read the evidence, ask for a
5
+ * Three routes, because there are exactly three things the surface does, read the evidence, ask for a
6
6
  * measurement to be retaken, and record what a turn concluded.
7
7
  *
8
8
  * There is no `GET /chores/{id}` and no "run this chore" route on purpose. A chore RUN is an ordinary isolated
9
9
  * fleet agent (`POST /agent` with a derived conversation id), the same as an acceptance run or a documentation
10
- * generation so the worktree, the live status, the cost, the transcript and the /agents/<id> page already
10
+ * generation, so the worktree, the live status, the cost, the transcript and the /agents/<id> page already
11
11
  * exist, and adding a bespoke launcher here would be a second way to start a turn that has to be kept in step
12
12
  * with the first. */
13
13
  export const choresContract = {
14
14
  // Every repo's standing evidence in one read: cached probe results (with their age and state), the cheap
15
15
  // resident signals, the ledger, and the daemon's node version. The rail badge polls this; so does the panel.
16
16
  list: oc.route({ method: "GET", path: "/chores" }).output(ChoresReportSchema),
17
- // Re-run one repo's probe now, ignoring its TTL the panel's per-probe refresh. An ack: the runner works in
17
+ // Re-run one repo's probe now, ignoring its TTL, the panel's per-probe refresh. An ack: the runner works in
18
18
  // the background and the result arrives on the next `list`, because a jscpd sweep outlives any sane request.
19
19
  probe: oc.route({ method: "POST", path: "/chores/probe" }).input(ChoreProbeRequestSchema).output(OkSchema),
20
20
  // Record what a chore turn concluded, or snooze one. Upsert by repo+chore: a chore has one current verdict,