local-review 0.2.0 → 0.4.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 (93) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/dist/demo/lib/gitworld.js +224 -0
  3. package/dist/demo/world/api.js +2436 -0
  4. package/dist/demo/world/infra.js +189 -0
  5. package/dist/demo/world/people.js +16 -0
  6. package/dist/demo/world/review.js +578 -0
  7. package/dist/demo/world/sdk.js +376 -0
  8. package/dist/demo/world/web.js +657 -0
  9. package/dist/server/cli.js +28 -0
  10. package/dist/server/config.js +3 -0
  11. package/dist/server/examples.js +204 -0
  12. package/dist/server/index.js +17 -2
  13. package/dist/server/projects.js +2 -0
  14. package/dist/web/assets/{angular-html-DJ4eInGE.js → angular-html-F7zRgWCZ.js} +1 -1
  15. package/dist/web/assets/{angular-ts-DBhgg2a7.js → angular-ts-BpEpytbw.js} +1 -1
  16. package/dist/web/assets/{apl-TCQAu201.js → apl-CXKG97CP.js} +1 -1
  17. package/dist/web/assets/{astro-DVtFuwC2.js → astro-Bbu7K9fw.js} +1 -1
  18. package/dist/web/assets/{blade-CMp-sZYg.js → blade-BIHnATAa.js} +1 -1
  19. package/dist/web/assets/{c-DgqspIM2.js → c-C-acQYm6.js} +1 -1
  20. package/dist/web/assets/{chapel-NTLt7i8E.js → chapel-CO4oLJ27.js} +1 -1
  21. package/dist/web/assets/{cobol-CJiWQrT4.js → cobol-DwOqj9Jg.js} +1 -1
  22. package/dist/web/assets/{coffee-Bv_hE6wf.js → coffee-D4-tkAkt.js} +1 -1
  23. package/dist/web/assets/{cpp-CuUsyAvc.js → cpp-B5AO2QHP.js} +1 -1
  24. package/dist/web/assets/{crystal-BeLopiD_.js → crystal-Da5vXEdt.js} +1 -1
  25. package/dist/web/assets/{css-OngwBV3K.js → css-BPZthOhS.js} +1 -1
  26. package/dist/web/assets/{edge-BFLildoE.js → edge-Dax-tr1_.js} +1 -1
  27. package/dist/web/assets/{elixir-1Fs6FjVs.js → elixir-Bn9yrJM6.js} +1 -1
  28. package/dist/web/assets/{elm-piMM67vF.js → elm-BOsm44l2.js} +1 -1
  29. package/dist/web/assets/{erb-JJ0sF8GL.js → erb-Gt3RYukx.js} +1 -1
  30. package/dist/web/assets/{git-rebase-CtqXywWe.js → git-rebase-PkordWam.js} +1 -1
  31. package/dist/web/assets/{glimmer-js-BPewWxGC.js → glimmer-js-B5msfXVo.js} +1 -1
  32. package/dist/web/assets/{glimmer-ts-DyUQWB47.js → glimmer-ts-CR148_Kt.js} +1 -1
  33. package/dist/web/assets/{glsl-D6tJoMaT.js → glsl-qur0cRcN.js} +1 -1
  34. package/dist/web/assets/{graphql-E6u2hkWE.js → graphql-C42K488Q.js} +1 -1
  35. package/dist/web/assets/{hack-Djq191-P.js → hack-NLFouBH4.js} +1 -1
  36. package/dist/web/assets/{haml-C0LqmZG3.js → haml-DKn_-I-I.js} +1 -1
  37. package/dist/web/assets/{handlebars-DwdxF6uN.js → handlebars-CQReOdSJ.js} +1 -1
  38. package/dist/web/assets/{html-derivative-DTVrLAw3.js → html-derivative-DoqJMb8S.js} +1 -1
  39. package/dist/web/assets/{html-B5Myt0fy.js → html-iUF_eP_4.js} +1 -1
  40. package/dist/web/assets/{http-CFoh3cQf.js → http-BJrCx5B5.js} +1 -1
  41. package/dist/web/assets/{hurl-B_38naAo.js → hurl-DlDfbRcL.js} +1 -1
  42. package/dist/web/assets/{index-CfxIojO0.js → index-CH6ceWie.js} +52 -52
  43. package/dist/web/assets/index-DU33o_Lu.css +1 -0
  44. package/dist/web/assets/{java-odnexk6r.js → java-DdxicsS9.js} +1 -1
  45. package/dist/web/assets/{javascript-DvFCJKws.js → javascript-Dw8rInq-.js} +1 -1
  46. package/dist/web/assets/{jinja-UeLt2DYu.js → jinja-ztBbs9jc.js} +1 -1
  47. package/dist/web/assets/{jison-HNaDYeqE.js → jison-DyOjkTQj.js} +1 -1
  48. package/dist/web/assets/{json-D57CnrjR.js → json-C6t0u9rP.js} +1 -1
  49. package/dist/web/assets/{jsx-o_5ZAydi.js → jsx-BpIc6zqG.js} +1 -1
  50. package/dist/web/assets/{julia-20UebbrD.js → julia-CvA9RPfz.js} +1 -1
  51. package/dist/web/assets/{just-DqnPCg-Q.js → just-CiH5trEM.js} +1 -1
  52. package/dist/web/assets/{latex-CZhIB-LV.js → latex-BbpuYBUm.js} +1 -1
  53. package/dist/web/assets/{liquid-BzouPsN0.js → liquid-Dy7K9saE.js} +1 -1
  54. package/dist/web/assets/{lua-BXclv8Fm.js → lua-CLN5DayT.js} +1 -1
  55. package/dist/web/assets/{marko-M78_INlG.js → marko-Be70_WI8.js} +1 -1
  56. package/dist/web/assets/{mdc-CBluOiY7.js → mdc-RgJIMLnK.js} +1 -1
  57. package/dist/web/assets/{nginx-CZFFQFbT.js → nginx-GhsGoiRj.js} +1 -1
  58. package/dist/web/assets/{nim-17kKsGqk.js → nim-BaIl7YzC.js} +1 -1
  59. package/dist/web/assets/{org-BZABHRQH.js → org-ksusUjYr.js} +1 -1
  60. package/dist/web/assets/{perl-CMrAQ9Je.js → perl-KX9noYTv.js} +1 -1
  61. package/dist/web/assets/{php-DwA3kETk.js → php-C-szMz57.js} +1 -1
  62. package/dist/web/assets/{pug-D4fM85kA.js → pug-nbUZSc0V.js} +1 -1
  63. package/dist/web/assets/{qml-C3893RHi.js → qml-BD2H6imu.js} +1 -1
  64. package/dist/web/assets/{r-DwJhLW77.js → r-CbR0TzPr.js} +1 -1
  65. package/dist/web/assets/{razor-_KkCNZ8V.js → razor-D_SYu_ob.js} +1 -1
  66. package/dist/web/assets/{regexp-BZ9NHg3b.js → regexp-BIEFfqXG.js} +1 -1
  67. package/dist/web/assets/{rst-gdK-1c4t.js → rst-D21NyilJ.js} +1 -1
  68. package/dist/web/assets/{ruby-DsmqYtlH.js → ruby-CCk7F-X_.js} +1 -1
  69. package/dist/web/assets/{sas-BNGv1ScF.js → sas-BasHIiX3.js} +1 -1
  70. package/dist/web/assets/{scss-DjYUXT73.js → scss-DL8S6L92.js} +1 -1
  71. package/dist/web/assets/{shellscript-DBtV-eli.js → shellscript-D-B4NAUX.js} +1 -1
  72. package/dist/web/assets/{shellsession-BKEV8Fnx.js → shellsession-LwE3hLIg.js} +1 -1
  73. package/dist/web/assets/{soy-DUapMxos.js → soy-DaUNAouD.js} +1 -1
  74. package/dist/web/assets/{sql-B-UyqKkd.js → sql-Drw6fxyA.js} +1 -1
  75. package/dist/web/assets/{stata-Xq3iZiMK.js → stata-Bk-VgNHo.js} +1 -1
  76. package/dist/web/assets/{surrealql-CzXacK8i.js → surrealql-BfOZjDkH.js} +1 -1
  77. package/dist/web/assets/{svelte-CZaBEmPP.js → svelte-BP8BXHT0.js} +1 -1
  78. package/dist/web/assets/{templ-CxkGuUpr.js → templ-BgcZlshd.js} +1 -1
  79. package/dist/web/assets/{tex-DP74JH6g.js → tex-BQczXiA6.js} +1 -1
  80. package/dist/web/assets/{ts-tags-BAcrCE2Y.js → ts-tags-CVFTjDsr.js} +1 -1
  81. package/dist/web/assets/{tsx-Dw11YP6z.js → tsx-j3JIiZaT.js} +1 -1
  82. package/dist/web/assets/{twig-BMVjBxD6.js → twig-D7saNHJy.js} +1 -1
  83. package/dist/web/assets/{typescript-DoBLlVdC.js → typescript-D62o9E27.js} +1 -1
  84. package/dist/web/assets/{typst-w2u_vl4_.js → typst-DQOeX7Fq.js} +1 -1
  85. package/dist/web/assets/{vue-BmuHTv5p.js → vue-BpdrgvY0.js} +1 -1
  86. package/dist/web/assets/{vue-html-Dt0zSF4I.js → vue-html-Cwi6ng_G.js} +1 -1
  87. package/dist/web/assets/{vue-vine-wvJ1BTex.js → vue-vine-BO8zdtYP.js} +1 -1
  88. package/dist/web/assets/{xml-CXftIBCQ.js → xml-DgdKyO3D.js} +1 -1
  89. package/dist/web/assets/{xsl-BoHSOMz1.js → xsl-CLXFRDPl.js} +1 -1
  90. package/dist/web/assets/{yaml-dGreldtD.js → yaml-BYa9ePnt.js} +1 -1
  91. package/dist/web/index.html +2 -2
  92. package/package.json +3 -2
  93. package/dist/web/assets/index-QywYYqTw.css +0 -1
@@ -0,0 +1,189 @@
1
+ // orchard-infra: Terraform for the cloud pieces, Kubernetes manifests for the API, and the feature flags file.
2
+ import { RepoBuilder, after, replace } from '../lib/gitworld.js';
3
+ import { at, june, maya, remote, sam, theo } from './people.js';
4
+ const append = (s) => (old) => old + s;
5
+ export function buildInfra() {
6
+ const r = new RepoBuilder('orchard-infra', remote('orchard-infra'));
7
+ const i1 = r.commit(null, 'Terraform for the database and network', {
8
+ 'README.md': `# orchard-infra
9
+
10
+ Everything Orchard runs on. Terraform (\`terraform/\`) owns the cloud resources; the API's Kubernetes
11
+ manifests live in \`k8s/\`; \`flags/flags.yaml\` is read by the API at startup.
12
+
13
+ Changes go through a PR like any code: CI runs \`terraform plan\` and posts it on the PR, and merging
14
+ to \`main\` applies it.
15
+ `,
16
+ 'terraform/main.tf': `terraform {
17
+ required_version = ">= 1.8"
18
+ required_providers {
19
+ aws = {
20
+ source = "hashicorp/aws"
21
+ version = "~> 5.60"
22
+ }
23
+ }
24
+ backend "s3" {
25
+ bucket = "orchard-terraform-state"
26
+ key = "prod/terraform.tfstate"
27
+ region = "eu-west-2"
28
+ }
29
+ }
30
+
31
+ provider "aws" {
32
+ region = var.region
33
+ default_tags {
34
+ tags = { project = "orchard", env = var.env }
35
+ }
36
+ }
37
+ `,
38
+ 'terraform/variables.tf': `variable "region" {
39
+ type = string
40
+ default = "eu-west-2"
41
+ }
42
+
43
+ variable "env" {
44
+ type = string
45
+ default = "prod"
46
+ }
47
+ `,
48
+ 'terraform/database.tf': `resource "aws_db_instance" "main" {
49
+ identifier = "orchard-\${var.env}"
50
+ engine = "postgres"
51
+ engine_version = "16.3"
52
+ instance_class = "db.t4g.medium"
53
+ allocated_storage = 50
54
+ max_allocated_storage = 200
55
+ db_name = "orchard"
56
+ username = "orchard"
57
+ manage_master_user_password = true
58
+ backup_retention_period = 14
59
+ deletion_protection = true
60
+ skip_final_snapshot = false
61
+ }
62
+ `,
63
+ }, { author: theo, at: at('2026-05-06 10:00') });
64
+ const i2 = r.commit(i1, 'Deploy the API to the cluster', {
65
+ 'k8s/api/deployment.yaml': `apiVersion: apps/v1
66
+ kind: Deployment
67
+ metadata:
68
+ name: orchard-api
69
+ labels: { app: orchard-api }
70
+ spec:
71
+ replicas: 3
72
+ selector:
73
+ matchLabels: { app: orchard-api }
74
+ template:
75
+ metadata:
76
+ labels: { app: orchard-api }
77
+ spec:
78
+ containers:
79
+ - name: api
80
+ image: registry.orchard.example/orchard-api:latest
81
+ ports:
82
+ - containerPort: 8080
83
+ envFrom:
84
+ - configMapRef: { name: orchard-api }
85
+ - secretRef: { name: orchard-api }
86
+ readinessProbe:
87
+ httpGet: { path: /healthz, port: 8080 }
88
+ resources:
89
+ requests: { cpu: 100m, memory: 128Mi }
90
+ limits: { memory: 256Mi }
91
+ `,
92
+ 'k8s/api/configmap.yaml': `apiVersion: v1
93
+ kind: ConfigMap
94
+ metadata:
95
+ name: orchard-api
96
+ data:
97
+ PORT: "8080"
98
+ SMTP_ADDR: "smtp.orchard.example:587"
99
+ MAIL_FROM: "bookings@orchard.example"
100
+ `,
101
+ }, { author: theo, at: at('2026-05-29 15:40') });
102
+ const i3 = r.commit(i2, 'Add the feature flags file', {
103
+ 'flags/flags.yaml': `# Read by orchard-api at startup. A flag is off unless listed here.
104
+ # studios: [ids] turns a flag on for those studios only; all: true for everyone.
105
+ flags:
106
+ class-spots-left:
107
+ all: true
108
+ `,
109
+ 'k8s/api/configmap.yaml': append(' FLAGS_FILE: "/etc/orchard/flags.yaml"\n'),
110
+ }, { author: june, at: at('2026-07-09 11:15') });
111
+ const i4 = r.commit(i3, 'Alert when webhook deliveries pile up', {
112
+ 'k8s/api/alerts.yaml': `apiVersion: monitoring.coreos.com/v1
113
+ kind: PrometheusRule
114
+ metadata:
115
+ name: orchard-api
116
+ spec:
117
+ groups:
118
+ - name: payments
119
+ rules:
120
+ - alert: WebhookDeadLetters
121
+ expr: orchard_webhook_dead_letters > 0
122
+ for: 10m
123
+ labels: { severity: page }
124
+ annotations:
125
+ summary: Payment webhooks are failing after every retry; members may be missing credits.
126
+ `,
127
+ }, { author: sam, at: at('2026-08-07 14:30') });
128
+ const i5 = r.commit(i4, 'Raise the API memory limit to 384Mi', {
129
+ 'k8s/api/deployment.yaml': replace('limits: { memory: 256Mi }', 'limits: { memory: 384Mi }'),
130
+ }, { author: theo, at: at('2026-08-21 09:05') });
131
+ // ---------------------------------------------------------------- Maya's waitlists branch
132
+ const f1 = r.commit(i5, 'Run the hold-expiry sweep every five minutes', {
133
+ 'k8s/api/cronjobs.yaml': `apiVersion: batch/v1
134
+ kind: CronJob
135
+ metadata:
136
+ name: orchard-sweep-holds
137
+ spec:
138
+ # Expires waitlist offers nobody took within the hold window and offers the spot to the next
139
+ # person in line (orchard-api: \`orchard-api sweep-holds\`). Holds are two hours, so a few minutes
140
+ # of lateness is fine; Forbid stops a slow run from overlapping the next.
141
+ schedule: "*/5 * * * *"
142
+ concurrencyPolicy: Forbid
143
+ successfulJobsHistoryLimit: 1
144
+ failedJobsHistoryLimit: 3
145
+ jobTemplate:
146
+ spec:
147
+ backoffLimit: 0
148
+ activeDeadlineSeconds: 240
149
+ template:
150
+ spec:
151
+ restartPolicy: Never
152
+ containers:
153
+ - name: sweep
154
+ image: registry.orchard.example/orchard-api:latest
155
+ args: ["sweep-holds"]
156
+ envFrom:
157
+ - configMapRef: { name: orchard-api }
158
+ - secretRef: { name: orchard-api }
159
+ resources:
160
+ requests: { cpu: 50m, memory: 64Mi }
161
+ limits: { memory: 128Mi }
162
+ `,
163
+ }, { author: maya, at: at('2026-09-11 17:05'), key: 'i1' });
164
+ const f2 = r.commit(f1, 'Alert when promotions stop going out', {
165
+ 'k8s/api/alerts.yaml': append(` - name: waitlists
166
+ rules:
167
+ - alert: HoldSweepFailing
168
+ expr: time() - kube_cronjob_status_last_successful_time{cronjob="orchard-sweep-holds"} > 1800
169
+ labels: { severity: ticket }
170
+ annotations:
171
+ summary: >-
172
+ The hold-expiry sweep hasn't succeeded for 30 minutes. Expired offers aren't being
173
+ passed on, so people further down waitlists aren't getting spots.
174
+ `),
175
+ }, { author: maya, at: at('2026-09-12 09:40'), key: 'i2' });
176
+ const f3 = r.commit(f2, 'Turn on waitlists for the beta studios', {
177
+ 'flags/flags.yaml': after(' class-spots-left:\n all: true\n', ` waitlists:
178
+ # the three studios that asked for it; everyone once we've watched a few weeks of promotions
179
+ studios: [4, 11, 17]
180
+ `),
181
+ }, { author: maya, at: at('2026-09-12 15:30'), key: 'i3' });
182
+ r.ref('main', i5);
183
+ r.ref('refs/remotes/origin/main', i5);
184
+ r.ref('refs/remotes/origin/HEAD', i5);
185
+ r.ref('maya/waitlists', f3);
186
+ r.ref('maya/infra-sweep-cron', f1);
187
+ r.ref('maya/infra-sweep-alert', f2);
188
+ return r;
189
+ }
@@ -0,0 +1,16 @@
1
+ export const maya = { name: 'Maya Okafor', email: 'maya@orchard.example' };
2
+ export const theo = { name: 'Theo Lindqvist', email: 'theo@orchard.example' };
3
+ export const sam = { name: 'Sam Reyes', email: 'sam@orchard.example' };
4
+ export const june = { name: 'June Park', email: 'june@orchard.example' };
5
+ /** The Local Review user the demo runs as (LOCAL_REVIEW_USER): the reviewer's comments are `author: maya`. */
6
+ export const REVIEWER = 'maya';
7
+ export const ORG = 'orchard-studio-demo';
8
+ export const remote = (repo) => `git@github.com:${ORG}/${repo}.git`;
9
+ export const prUrl = (repo, n) => `https://github.com/${ORG}/${repo}/pull/${n}`;
10
+ /** "2026-09-14 10:02" → an ISO UTC timestamp. */
11
+ export const at = (s) => `${s.replace(' ', 'T')}:00Z`;
12
+ /**
13
+ * "Now" for the demo: the capture rig freezes the browser clock here, so relative times ("2 days ago") are the same
14
+ * in every screenshot, and the seed sets file times relative to it.
15
+ */
16
+ export const DEMO_NOW = '2026-09-18T16:30:00Z';