redlinegate 0.0.1

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 (198) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +188 -0
  3. package/commands/redline-init.md +54 -0
  4. package/commands/redline-verify.md +69 -0
  5. package/dist/bin/redline.js +605 -0
  6. package/dist/bin/redline.js.map +1 -0
  7. package/dist/commands/exempt.js +45 -0
  8. package/dist/commands/exempt.js.map +1 -0
  9. package/dist/commands/init.js +615 -0
  10. package/dist/commands/init.js.map +1 -0
  11. package/dist/commands/policy.js +25 -0
  12. package/dist/commands/policy.js.map +1 -0
  13. package/dist/commands/remove.js +427 -0
  14. package/dist/commands/remove.js.map +1 -0
  15. package/dist/commands/review.js +72 -0
  16. package/dist/commands/review.js.map +1 -0
  17. package/dist/commands/sync.js +42 -0
  18. package/dist/commands/sync.js.map +1 -0
  19. package/dist/commands/verify.js +478 -0
  20. package/dist/commands/verify.js.map +1 -0
  21. package/dist/config/redline-json.js +187 -0
  22. package/dist/config/redline-json.js.map +1 -0
  23. package/dist/core/errors.js +25 -0
  24. package/dist/core/errors.js.map +1 -0
  25. package/dist/core/git.js +152 -0
  26. package/dist/core/git.js.map +1 -0
  27. package/dist/core/log.js +22 -0
  28. package/dist/core/log.js.map +1 -0
  29. package/dist/core/severity.js +16 -0
  30. package/dist/core/severity.js.map +1 -0
  31. package/dist/core/version.js +15 -0
  32. package/dist/core/version.js.map +1 -0
  33. package/dist/detect/scan.js +79 -0
  34. package/dist/detect/scan.js.map +1 -0
  35. package/dist/detect/stack.js +71 -0
  36. package/dist/detect/stack.js.map +1 -0
  37. package/dist/enforce/ladder.js +148 -0
  38. package/dist/enforce/ladder.js.map +1 -0
  39. package/dist/exempt/parse.js +86 -0
  40. package/dist/exempt/parse.js.map +1 -0
  41. package/dist/metrics/options.js +257 -0
  42. package/dist/metrics/options.js.map +1 -0
  43. package/dist/metrics/run.js +72 -0
  44. package/dist/metrics/run.js.map +1 -0
  45. package/dist/platforms/azure/client.js +53 -0
  46. package/dist/platforms/azure/client.js.map +1 -0
  47. package/dist/platforms/azure/index.js +69 -0
  48. package/dist/platforms/azure/index.js.map +1 -0
  49. package/dist/platforms/azure/install.js +917 -0
  50. package/dist/platforms/azure/install.js.map +1 -0
  51. package/dist/platforms/azure/policy-types.js +70 -0
  52. package/dist/platforms/azure/policy-types.js.map +1 -0
  53. package/dist/platforms/azure/verify.js +309 -0
  54. package/dist/platforms/azure/verify.js.map +1 -0
  55. package/dist/platforms/detect.js +40 -0
  56. package/dist/platforms/detect.js.map +1 -0
  57. package/dist/platforms/github/client.js +49 -0
  58. package/dist/platforms/github/client.js.map +1 -0
  59. package/dist/platforms/github/index.js +54 -0
  60. package/dist/platforms/github/index.js.map +1 -0
  61. package/dist/platforms/github/install.js +558 -0
  62. package/dist/platforms/github/install.js.map +1 -0
  63. package/dist/platforms/github/push.js +102 -0
  64. package/dist/platforms/github/push.js.map +1 -0
  65. package/dist/platforms/github/remote.js +36 -0
  66. package/dist/platforms/github/remote.js.map +1 -0
  67. package/dist/platforms/github/verify.js +366 -0
  68. package/dist/platforms/github/verify.js.map +1 -0
  69. package/dist/platforms/http.js +58 -0
  70. package/dist/platforms/http.js.map +1 -0
  71. package/dist/platforms/pull-request-templates.js +157 -0
  72. package/dist/platforms/pull-request-templates.js.map +1 -0
  73. package/dist/platforms/remote.js +21 -0
  74. package/dist/platforms/remote.js.map +1 -0
  75. package/dist/platforms/resolve.js +69 -0
  76. package/dist/platforms/resolve.js.map +1 -0
  77. package/dist/platforms/shape.js +19 -0
  78. package/dist/platforms/shape.js.map +1 -0
  79. package/dist/platforms/types.js +24 -0
  80. package/dist/platforms/types.js.map +1 -0
  81. package/dist/policy/checks.js +91 -0
  82. package/dist/policy/checks.js.map +1 -0
  83. package/dist/policy/diff.js +50 -0
  84. package/dist/policy/diff.js.map +1 -0
  85. package/dist/registry/discover.js +74 -0
  86. package/dist/registry/discover.js.map +1 -0
  87. package/dist/registry/serialize.js +41 -0
  88. package/dist/registry/serialize.js.map +1 -0
  89. package/dist/registry/types.js +2 -0
  90. package/dist/registry/types.js.map +1 -0
  91. package/dist/remove/host.js +294 -0
  92. package/dist/remove/host.js.map +1 -0
  93. package/dist/render/commands.js +139 -0
  94. package/dist/render/commands.js.map +1 -0
  95. package/dist/render/manifest.js +82 -0
  96. package/dist/render/manifest.js.map +1 -0
  97. package/dist/render/markers.js +166 -0
  98. package/dist/render/markers.js.map +1 -0
  99. package/dist/render/profile.js +23 -0
  100. package/dist/render/profile.js.map +1 -0
  101. package/dist/render/standards.js +169 -0
  102. package/dist/render/standards.js.map +1 -0
  103. package/dist/render/vendors.js +199 -0
  104. package/dist/render/vendors.js.map +1 -0
  105. package/dist/review/engines/api.js +95 -0
  106. package/dist/review/engines/api.js.map +1 -0
  107. package/dist/review/engines/embedded.js +20 -0
  108. package/dist/review/engines/embedded.js.map +1 -0
  109. package/dist/review/engines/types.js +2 -0
  110. package/dist/review/engines/types.js.map +1 -0
  111. package/dist/review/glob.js +29 -0
  112. package/dist/review/glob.js.map +1 -0
  113. package/dist/review/prompt.js +45 -0
  114. package/dist/review/prompt.js.map +1 -0
  115. package/dist/review/rules.js +25 -0
  116. package/dist/review/rules.js.map +1 -0
  117. package/dist/review/schema.js +84 -0
  118. package/dist/review/schema.js.map +1 -0
  119. package/dist/review/scope.js +27 -0
  120. package/dist/review/scope.js.map +1 -0
  121. package/dist/sarif/map.js +58 -0
  122. package/dist/sarif/map.js.map +1 -0
  123. package/dist/sarif/parse.js +88 -0
  124. package/dist/sarif/parse.js.map +1 -0
  125. package/dist/sarif/types.js +2 -0
  126. package/dist/sarif/types.js.map +1 -0
  127. package/dist/sync/host.js +21 -0
  128. package/dist/sync/host.js.map +1 -0
  129. package/dist/sync/plan.js +47 -0
  130. package/dist/sync/plan.js.map +1 -0
  131. package/dist/sync/render.js +57 -0
  132. package/dist/sync/render.js.map +1 -0
  133. package/dist/sync/run.js +118 -0
  134. package/dist/sync/run.js.map +1 -0
  135. package/dist/verify/host.js +52 -0
  136. package/dist/verify/host.js.map +1 -0
  137. package/dist/verify/remote.js +146 -0
  138. package/dist/verify/remote.js.map +1 -0
  139. package/package.json +52 -0
  140. package/platforms/azure/gate-template.yml +105 -0
  141. package/rulesets/redline-org-ruleset.json +46 -0
  142. package/rulesets/redline-ruleset.json +49 -0
  143. package/scripts/assign-rule-ids.mjs +129 -0
  144. package/scripts/build-baseline.mjs +124 -0
  145. package/scripts/build-correlation.mjs +74 -0
  146. package/scripts/build-dashboard.mjs +584 -0
  147. package/scripts/build-digest.mjs +164 -0
  148. package/scripts/build-inbox.mjs +133 -0
  149. package/scripts/build-registry.mjs +42 -0
  150. package/scripts/build-roi.mjs +181 -0
  151. package/scripts/check-pins.mjs +88 -0
  152. package/scripts/collect-telemetry.mjs +300 -0
  153. package/scripts/lib/__tests__/baseline.test.mjs +132 -0
  154. package/scripts/lib/__tests__/correlate.test.mjs +131 -0
  155. package/scripts/lib/__tests__/dora.test.mjs +110 -0
  156. package/scripts/lib/__tests__/exemptions.test.mjs +85 -0
  157. package/scripts/lib/__tests__/metrics-scanner.test.mjs +65 -0
  158. package/scripts/lib/__tests__/sarif.test.mjs +125 -0
  159. package/scripts/lib/__tests__/spend.test.mjs +64 -0
  160. package/scripts/lib/baseline.mjs +156 -0
  161. package/scripts/lib/correlate.mjs +125 -0
  162. package/scripts/lib/dora.mjs +133 -0
  163. package/scripts/lib/exemptions.mjs +79 -0
  164. package/scripts/lib/metrics.mjs +172 -0
  165. package/scripts/lib/rules.mjs +107 -0
  166. package/scripts/lib/sarif.mjs +108 -0
  167. package/scripts/lib/spend.mjs +69 -0
  168. package/scripts/measure-context.mjs +101 -0
  169. package/scripts/render-self.mjs +18 -0
  170. package/scripts/score-seeds.mjs +294 -0
  171. package/scripts/validate.mjs +394 -0
  172. package/standards/core.md +124 -0
  173. package/standards/manifest.json +214 -0
  174. package/standards/stacks/csharp.md +29 -0
  175. package/standards/stacks/go.md +30 -0
  176. package/standards/stacks/java.md +30 -0
  177. package/standards/stacks/javascript.md +42 -0
  178. package/standards/stacks/kotlin.md +27 -0
  179. package/standards/stacks/microservices.md +37 -0
  180. package/standards/stacks/nodejs.md +34 -0
  181. package/standards/stacks/python.md +29 -0
  182. package/standards/stacks/react-native.md +29 -0
  183. package/standards/stacks/react.md +46 -0
  184. package/standards/stacks/swift.md +26 -0
  185. package/standards/stacks/terraform.md +27 -0
  186. package/templates/CODEOWNERS +29 -0
  187. package/templates/azure/pull_request_template.md +72 -0
  188. package/templates/github/pull_request_template.md +72 -0
  189. package/templates/redline.yml +35 -0
  190. package/templates/repo-context.md +64 -0
  191. package/workflows/dashboard.yml +119 -0
  192. package/workflows/inbox.yml +97 -0
  193. package/workflows/redline-collect.yml +61 -0
  194. package/workflows/redline-gate.yml +301 -0
  195. package/workflows/redline-sync.yml +79 -0
  196. package/workflows/seed-canary.yml +231 -0
  197. package/workflows/verify-onboarding.yml +115 -0
  198. package/workflows/weekly-digest.yml +78 -0
@@ -0,0 +1,148 @@
1
+ // The enforcement ladder.
2
+ //
3
+ // Enforcement was binary: advisory by default, blocking with `--blocking`.
4
+ // Neither end works across hundreds of repositories. Rolling blocking to all of
5
+ // them in one step is not achievable; leaving everything advisory means the
6
+ // organisation can never state a guarantee about any of them.
7
+ //
8
+ // Four rungs, and a repository climbs on recorded evidence rather than on
9
+ // assertion. Two decisions the roadmap left open are settled here, and both are
10
+ // settled toward the same principle: **the safe direction never needs permission.**
11
+ export const RUNGS = ['observe', 'warn', 'block-blocker', 'block-high'];
12
+ export const RUNG_INDEX = {
13
+ observe: 0,
14
+ warn: 1,
15
+ 'block-blocker': 2,
16
+ 'block-high': 3,
17
+ };
18
+ export const isRung = (value) => typeof value === 'string' && RUNGS.includes(value);
19
+ export const BEHAVIOUR = {
20
+ observe: {
21
+ blocks: false,
22
+ description: 'Findings are reported and recorded. Nothing is blocked, including a BLOCKER.',
23
+ },
24
+ warn: {
25
+ blocks: false,
26
+ description: 'Findings are reported as warnings on the pull request. Still nothing is blocked, but the check is visible in the merge box rather than buried in a log.',
27
+ },
28
+ 'block-blocker': {
29
+ blocks: 'BLOCKER',
30
+ description: 'A BLOCKER finding stops the merge. HIGH and SUGGESTION are reported.',
31
+ },
32
+ 'block-high': {
33
+ blocks: 'HIGH',
34
+ description: 'A BLOCKER or a HIGH stops the merge. The strictest rung, and the one that needs the most trust in the rule set.',
35
+ },
36
+ };
37
+ export const REQUIREMENTS = {
38
+ // Nothing to earn: observe is where a repository starts.
39
+ observe: { minSeedRecall: null, minActedOnRate: null, minSampleSize: 0, maxFalsePositives: null },
40
+ // Warning costs nobody a merge, so it asks only that the reviewer is running.
41
+ warn: { minSeedRecall: null, minActedOnRate: null, minSampleSize: 5, maxFalsePositives: null },
42
+ // The first rung that can stop a merge, and the first that needs proof the
43
+ // reviewer both catches real defects and is not flagging correct code.
44
+ 'block-blocker': {
45
+ minSeedRecall: 1,
46
+ minActedOnRate: 0.6,
47
+ minSampleSize: 20,
48
+ maxFalsePositives: 0,
49
+ },
50
+ // Blocking on HIGH means blocking on judgement calls, so the bar for people
51
+ // actually agreeing with the findings is materially higher.
52
+ 'block-high': {
53
+ minSeedRecall: 1,
54
+ minActedOnRate: 0.8,
55
+ minSampleSize: 50,
56
+ maxFalsePositives: 0,
57
+ },
58
+ };
59
+ export function nextRung(current) {
60
+ return RUNGS[RUNG_INDEX[current] + 1] ?? null;
61
+ }
62
+ /**
63
+ * May this repository move to `to`?
64
+ *
65
+ * **Demotion never needs evidence.** Moving down is always allowed and always
66
+ * immediate: a repository whose gate is misfiring at 3am must be able to step
67
+ * back without waiting for anyone, and a ladder that made the safe direction hard
68
+ * would be switched off entirely rather than stepped down.
69
+ *
70
+ * **A market floor may raise a repository's minimum, never lower it below its
71
+ * own choice.** Markets have different regulators and different appetites, so the
72
+ * ladder is per repository with a per-market floor — a market may insist on more
73
+ * than a repository wants, and may not force one below where it has already got to.
74
+ */
75
+ export function canPromote(from, to, evidence, marketFloor = 'observe') {
76
+ const blockers = [];
77
+ if (RUNG_INDEX[to] < RUNG_INDEX[from]) {
78
+ if (RUNG_INDEX[to] < RUNG_INDEX[marketFloor]) {
79
+ return {
80
+ to,
81
+ eligible: false,
82
+ blockers: [
83
+ `this market's floor is "${marketFloor}" — a repository may not go below it. ` +
84
+ 'Raise it with the market owner, not here.',
85
+ ],
86
+ };
87
+ }
88
+ // Down, and at or above the floor. Always allowed, no evidence asked for.
89
+ return { to, eligible: true, blockers: [] };
90
+ }
91
+ if (to === from)
92
+ return { to, eligible: true, blockers: [] };
93
+ // One rung at a time. Jumping observe to block-high skips the rung where the
94
+ // evidence for blocking is actually gathered.
95
+ if (RUNG_INDEX[to] > RUNG_INDEX[from] + 1) {
96
+ blockers.push(`"${from}" cannot jump to "${to}" — promote one rung at a time, through "${nextRung(from)}", ` +
97
+ 'which is where the evidence for the next one is gathered');
98
+ }
99
+ const need = REQUIREMENTS[to];
100
+ if (evidence.sampleSize < need.minSampleSize) {
101
+ blockers.push(`${need.minSampleSize} reviewed pull requests are needed and ${evidence.sampleSize} have been recorded — ` +
102
+ 'a perfect rate over a handful is a coincidence, not evidence');
103
+ }
104
+ if (need.minSeedRecall !== null) {
105
+ if (evidence.seedRecall === null) {
106
+ blockers.push('the seeded corpus has never been scored for this stack, so there is no evidence the reviewer ' +
107
+ 'catches known defects at all');
108
+ }
109
+ else if (evidence.seedRecall < need.minSeedRecall) {
110
+ blockers.push(`seed BLOCKER recall is ${(evidence.seedRecall * 100).toFixed(0)}% and ` +
111
+ `${(need.minSeedRecall * 100).toFixed(0)}% is required — a reviewer that misses known defects ` +
112
+ 'must not be given a veto');
113
+ }
114
+ }
115
+ if (need.maxFalsePositives !== null) {
116
+ if (evidence.falsePositives === null) {
117
+ blockers.push('the clean corpus has never been scored, so nothing shows the reviewer stays quiet on correct code');
118
+ }
119
+ else if (evidence.falsePositives > need.maxFalsePositives) {
120
+ blockers.push(`${evidence.falsePositives} false positive(s) on the clean corpus — a reviewer that flags correct ` +
121
+ 'code cannot be given a veto, whatever its recall');
122
+ }
123
+ }
124
+ if (need.minActedOnRate !== null) {
125
+ if (evidence.actedOnRate === null) {
126
+ blockers.push('too few findings have fired to compute an acted-on rate');
127
+ }
128
+ else if (evidence.actedOnRate < need.minActedOnRate) {
129
+ blockers.push(`acted-on rate is ${(evidence.actedOnRate * 100).toFixed(0)}% and ` +
130
+ `${(need.minActedOnRate * 100).toFixed(0)}% is required — blocking on findings people already ` +
131
+ 'ignore turns the gate into an obstacle rather than a control');
132
+ }
133
+ }
134
+ return { to, eligible: blockers.length === 0, blockers };
135
+ }
136
+ /** The highest rung this repository could move to today, and why not higher. */
137
+ export function ladderStatus(current, evidence, marketFloor = 'observe') {
138
+ const next = nextRung(current);
139
+ return {
140
+ current,
141
+ next,
142
+ promotion: next ? canPromote(current, next, evidence, marketFloor) : null,
143
+ // A repository under its market's floor is not drifting, it is out of policy,
144
+ // and the difference matters to whoever has to act.
145
+ belowFloor: RUNG_INDEX[current] < RUNG_INDEX[marketFloor],
146
+ };
147
+ }
148
+ //# sourceMappingURL=ladder.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ladder.js","sourceRoot":"","sources":["../../cli/enforce/ladder.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,86 @@
1
+ // A structured exemption: who accepted a failing process check, why, and until when.
2
+ //
3
+ // The bare `redline-exempt` label recorded none of that. It downgraded process
4
+ // checks to warnings and left the estate unable to answer the two questions that
5
+ // matter about an exemption — is anyone still standing behind it, and was it ever
6
+ // meant to be permanent. An exemption nobody has to justify and nobody revisits
7
+ // is not an exemption, it is an opt-out.
8
+ export const EXEMPTION_HEADING = '## Redline exemption';
9
+ // Ninety days. Long enough for real remediation work to be scheduled, short
10
+ // enough that "temporary" has to be renewed by someone who still believes it.
11
+ export const MAX_DAYS = 90;
12
+ export const MIN_REASON_LENGTH = 20;
13
+ // HTML comments are guidance, not content.
14
+ //
15
+ // The shipped pull request template explains each field with a commented block
16
+ // that necessarily contains the words `reason:`, `until:` and `scope:` — so a
17
+ // parser reading the first match found the instructions instead of the author's
18
+ // answer, and rejected every correctly filled exemption. The template cannot
19
+ // stop describing its own fields, so the parser has to know what a comment is.
20
+ const withoutComments = (text) => text.replace(/<!--[\s\S]*?-->/g, '');
21
+ const field = (block, name) => {
22
+ const match = new RegExp(`^\\s*[-*]?\\s*${name}\\s*:\\s*(.+)$`, 'im').exec(block);
23
+ return match?.[1]?.trim() ?? null;
24
+ };
25
+ /**
26
+ * Parse the exemption block out of a pull request body.
27
+ *
28
+ * `now` is injected rather than read from the clock so the expiry rules are
29
+ * testable and so a gate run and a later audit of the same pull request can
30
+ * reach the same verdict about the same text.
31
+ */
32
+ export function parseExemption(body, now) {
33
+ const problems = [];
34
+ const fail = (problem, detail) => {
35
+ problems.push({ problem, detail });
36
+ return { exemption: null, problems };
37
+ };
38
+ const searchable = withoutComments(body);
39
+ const start = searchable.toLowerCase().indexOf(EXEMPTION_HEADING.toLowerCase());
40
+ if (start === -1) {
41
+ return fail('no-block', `the pull request has no "${EXEMPTION_HEADING}" section — a label alone no longer exempts anything`);
42
+ }
43
+ // Everything until the next heading of the same or higher level. A reason that
44
+ // runs into the next section would otherwise swallow the rest of the body.
45
+ const rest = searchable.slice(start + EXEMPTION_HEADING.length);
46
+ const next = /^#{1,2}\s/m.exec(rest);
47
+ const block = next ? rest.slice(0, next.index) : rest;
48
+ const reason = field(block, 'reason');
49
+ if (!reason) {
50
+ return fail('no-reason', 'the exemption block has no `reason:` — say what is being accepted and why');
51
+ }
52
+ if (reason.length < MIN_REASON_LENGTH) {
53
+ return fail('reason-too-short', `the reason is ${reason.length} characters; at least ${MIN_REASON_LENGTH} are required — "needed for release" tells a later reader nothing`);
54
+ }
55
+ const until = field(block, 'until');
56
+ if (!until) {
57
+ return fail('no-until', 'the exemption block has no `until:` — an exemption with no end is a permanent one nobody chose');
58
+ }
59
+ const parsed = new Date(`${until}T23:59:59.999Z`);
60
+ if (Number.isNaN(parsed.getTime())) {
61
+ return fail('until-unparseable', `\`until: ${until}\` is not a YYYY-MM-DD date`);
62
+ }
63
+ if (parsed.getTime() < now.getTime()) {
64
+ return fail('until-past', `this exemption expired on ${until} — renew it deliberately or fix the finding`);
65
+ }
66
+ // Ceil, not the raw fraction: `until` is a whole day and is inclusive to its
67
+ // end, so a date exactly MAX_DAYS out measures as slightly over and was
68
+ // refused as "91 days away" for asking for 90.
69
+ const days = Math.ceil((parsed.getTime() - now.getTime()) / 86400000 - 1);
70
+ if (days > MAX_DAYS) {
71
+ return fail('until-too-far', `\`until: ${until}\` is ${Math.round(days)} days away; the maximum is ${MAX_DAYS} — a longer exemption is a standards change, not an exemption`);
72
+ }
73
+ const scopeField = field(block, 'scope');
74
+ const scope = scopeField
75
+ ? scopeField
76
+ .split(',')
77
+ .map((s) => s.trim())
78
+ .filter((s) => s !== '')
79
+ : ['*'];
80
+ return { exemption: { reason, until, scope }, problems };
81
+ }
82
+ /** Whether an exemption covers a named check or rule id. */
83
+ export function covers(exemption, check) {
84
+ return exemption.scope.includes('*') || exemption.scope.includes(check);
85
+ }
86
+ //# sourceMappingURL=parse.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parse.js","sourceRoot":"","sources":["../../cli/exempt/parse.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,257 @@
1
+ import { RedlineError } from "../core/errors.js";
2
+ const DATA = {
3
+ env: 'DATA_DIR',
4
+ type: 'path',
5
+ help: 'directory of collected telemetry',
6
+ default: 'data',
7
+ };
8
+ const ORG = { env: 'ORG', type: 'string', help: 'the GitHub organisation', required: true };
9
+ const TOKEN = {
10
+ env: 'GH_TOKEN',
11
+ type: 'string',
12
+ help: 'a token with org read access (or set GH_TOKEN in the environment)',
13
+ };
14
+ export const METRICS_COMMANDS = {
15
+ collect: {
16
+ summary: 'pull review outcomes for merged pull requests across the org',
17
+ runsIn: 'the metrics repo, on a schedule',
18
+ script: 'scripts/collect-telemetry.mjs',
19
+ options: {
20
+ org: ORG,
21
+ token: TOKEN,
22
+ days: { env: 'DAYS', type: 'number', help: 'how far back to look', default: '8' },
23
+ since: { env: 'SINCE', type: 'string', help: 'YYYY-MM-DD, instead of --days' },
24
+ out: { env: 'OUT', type: 'path', help: 'where to write the monthly JSONL', default: 'data' },
25
+ 'dry-run': { env: 'DRY_RUN', type: 'boolean', help: 'print a sample and write nothing' },
26
+ 'skip-sarif': { env: 'SKIP_SARIF', type: 'boolean', help: 'do not ingest code-scanning alerts' },
27
+ },
28
+ },
29
+ dashboard: {
30
+ summary: 'build the telemetry dashboard page',
31
+ runsIn: 'the metrics repo, or any checkout of its data/',
32
+ script: 'scripts/build-dashboard.mjs',
33
+ options: {
34
+ org: ORG,
35
+ data: DATA,
36
+ days: { env: 'DAYS', type: 'number', help: 'window in days', default: '90' },
37
+ out: { env: 'OUT', type: 'path', help: 'output directory', default: 'dist' },
38
+ registry: { env: 'REGISTRY', type: 'path', help: 'the register, for coverage and the ladder', default: 'registry.json' },
39
+ onboarded: { env: 'ONBOARDED', type: 'number', help: 'onboarded repo count, if not read from the register' },
40
+ 'seed-scores': { env: 'SEED_SCORES', type: 'path', help: 'seed score history', default: 'data/seed-scores.jsonl' },
41
+ 'standards-version': { env: 'STANDARDS_VERSION', type: 'string', help: 'version to display' },
42
+ },
43
+ },
44
+ digest: {
45
+ summary: 'build the weekly stakeholder digest as an Adaptive Card',
46
+ runsIn: 'the metrics repo, weekly',
47
+ script: 'scripts/build-digest.mjs',
48
+ options: {
49
+ org: ORG,
50
+ data: DATA,
51
+ days: { env: 'DAYS', type: 'number', help: 'window in days', default: '7' },
52
+ out: { env: 'OUT', type: 'path', help: 'write the card here instead of stdout' },
53
+ 'open-prs': { env: 'OPEN_PRS', type: 'number', help: 'open PR count, from the caller' },
54
+ 'stale-prs': { env: 'STALE_PRS', type: 'number', help: 'stale PR count, from the caller' },
55
+ 'seed-scores': { env: 'SEED_SCORES', type: 'path', help: 'seed score history, for the recall line' },
56
+ 'dashboard-url': { env: 'DASHBOARD_URL', type: 'string', help: 'linked from the card' },
57
+ },
58
+ },
59
+ inbox: {
60
+ summary: 'build the org-wide prioritised pull request inbox',
61
+ runsIn: 'the source repo, on a schedule',
62
+ script: 'scripts/build-inbox.mjs',
63
+ options: {
64
+ org: ORG,
65
+ token: TOKEN,
66
+ out: { env: 'OUT', type: 'path', help: 'output directory', default: 'dist' },
67
+ 'max-pages': { env: 'MAX_PAGES', type: 'number', help: 'search pages to walk', default: '10' },
68
+ },
69
+ },
70
+ baseline: {
71
+ summary: 'compute the baseline every roadmap phase is measured against',
72
+ runsIn: 'a maintainer terminal, once, with org credentials',
73
+ script: 'scripts/build-baseline.mjs',
74
+ options: {
75
+ data: DATA,
76
+ org: { env: 'ORG', type: 'string', help: 'the org, to survey Redline\'s own PRs and SARIF producers' },
77
+ token: TOKEN,
78
+ registry: { env: 'REGISTRY', type: 'path', help: 'the derived register', default: 'registry.json' },
79
+ days: { env: 'DAYS', type: 'number', help: 'window in days', default: '90' },
80
+ out: { env: 'OUT', type: 'path', help: 'where to write the JSON', default: 'baseline.json' },
81
+ 'spend-total': { env: 'SPEND_TOTAL', type: 'number', help: 'AI spend, from the vendor\'s own usage reporting' },
82
+ 'spend-currency': { env: 'SPEND_CURRENCY', type: 'string', help: 'currency of --spend-total', default: 'USD' },
83
+ 'spend-grain': {
84
+ env: 'SPEND_GRAIN',
85
+ type: 'string',
86
+ help: 'whether the spend figure is per repo or org-wide',
87
+ default: 'org',
88
+ values: ['repo', 'org', 'unknown'],
89
+ },
90
+ },
91
+ },
92
+ roi: {
93
+ summary: 'build the page that says what review cost against what it caught',
94
+ runsIn: 'the metrics repo, or any checkout of its data/',
95
+ script: 'scripts/build-roi.mjs',
96
+ options: {
97
+ data: DATA,
98
+ days: { env: 'DAYS', type: 'number', help: 'window in days', default: '90' },
99
+ out: { env: 'OUT', type: 'path', help: 'JSON output', default: 'roi.json' },
100
+ html: { env: 'HTML', type: 'path', help: 'the readable page', default: 'roi.html' },
101
+ 'spend-total': { env: 'SPEND_TOTAL', type: 'number', help: 'AI spend for the window' },
102
+ 'spend-currency': { env: 'SPEND_CURRENCY', type: 'string', help: 'currency of --spend-total', default: 'USD' },
103
+ 'spend-grain': {
104
+ env: 'SPEND_GRAIN',
105
+ type: 'string',
106
+ help: 'per repo or org-wide — an org figure will not answer a per-repo question',
107
+ default: 'org',
108
+ values: ['repo', 'org', 'unknown'],
109
+ },
110
+ 'spend-source': { env: 'SPEND_SOURCE', type: 'string', help: 'where the figure came from, recorded with it' },
111
+ },
112
+ },
113
+ correlate: {
114
+ summary: 'research: whether ignoring a finding cost anything',
115
+ runsIn: 'the metrics repo. It is an experiment, not a loop',
116
+ script: 'scripts/build-correlation.mjs',
117
+ options: {
118
+ data: DATA,
119
+ days: { env: 'DAYS', type: 'number', help: 'how much history to read', default: '180' },
120
+ 'window-days': { env: 'WINDOW_DAYS', type: 'number', help: 'how long after a merge a revert still counts', default: '30' },
121
+ 'min-sample': {
122
+ env: 'MIN_SAMPLE',
123
+ type: 'number',
124
+ help: 'ignored findings a rule needs before any rate is reported',
125
+ default: '10',
126
+ },
127
+ out: { env: 'OUT', type: 'path', help: 'where to write the JSON', default: 'correlation.json' },
128
+ },
129
+ },
130
+ 'score-seeds': {
131
+ summary: 'score an automated reviewer against the seeded corpus',
132
+ runsIn: 'anywhere, against a pull request that carries the corpus',
133
+ script: 'scripts/score-seeds.mjs',
134
+ argv: true,
135
+ options: {
136
+ repo: { env: 'REPO', type: 'string', help: 'owner/name of the pilot repository', required: true },
137
+ pr: { env: 'PR', type: 'number', help: 'the pull request number', required: true },
138
+ token: TOKEN,
139
+ json: { env: 'JSON_OUT', type: 'boolean', help: 'machine-readable output' },
140
+ window: { env: 'WINDOW', type: 'number', help: 'lines around a marker a finding may land on', default: '4' },
141
+ history: { env: 'HISTORY', type: 'path', help: 'append the score to this JSONL' },
142
+ baseline: { env: 'BASELINE', type: 'boolean', help: 'record as a baseline rather than a run' },
143
+ },
144
+ },
145
+ context: {
146
+ summary: 'measure what the skills render target saves, per profile',
147
+ runsIn: 'the source repo',
148
+ script: 'scripts/measure-context.mjs',
149
+ options: {
150
+ root: { env: 'ROOT', type: 'path', help: 'the repo to measure', default: '.' },
151
+ out: { env: 'OUT', type: 'path', help: 'also write the rows as JSON here' },
152
+ },
153
+ },
154
+ };
155
+ export const REGISTRY_COMMAND = {
156
+ summary: 'derive the register of onboarded repositories by walking the org',
157
+ runsIn: 'the source repo, nightly',
158
+ script: 'scripts/build-registry.mjs',
159
+ options: {
160
+ org: ORG,
161
+ token: TOKEN,
162
+ source: {
163
+ env: 'SOURCE',
164
+ type: 'string',
165
+ help: 'owner/name of this repo, recorded so a consumer knows which estate the register describes',
166
+ required: true,
167
+ },
168
+ out: { env: 'OUT', type: 'path', help: 'where to write it', default: 'registry.json' },
169
+ },
170
+ };
171
+ /**
172
+ * Turn parsed flags into the environment the runner reads.
173
+ *
174
+ * Every refusal here is one that used to be silent. An unset required option was
175
+ * a runner throwing halfway through; a non-numeric `--days` became `NaN` and
176
+ * produced an empty window; a mistyped `--spend-grain` became "unknown" and made
177
+ * the resulting number quietly less trustworthy than it looked.
178
+ */
179
+ export function resolveEnv(spec, flags, env, name) {
180
+ const resolved = {};
181
+ for (const [flag, option] of Object.entries(spec.options)) {
182
+ const raw = flags[flag];
183
+ if (option.type === 'boolean') {
184
+ if (raw === true)
185
+ resolved[option.env] = '1';
186
+ continue;
187
+ }
188
+ // Precedence: the flag, then the environment, then the default. The
189
+ // environment stays honoured so a scheduled job can keep using a secret
190
+ // without putting a token on a command line, where it lands in shell history
191
+ // and in the process table.
192
+ const value = raw !== undefined ? String(raw) : (env[option.env] ?? option.default);
193
+ if (value === undefined || value === '') {
194
+ if (option.required) {
195
+ throw new RedlineError('usage', `redline ${name} needs --${flag} (${option.help})`, `or set ${option.env} in the environment`);
196
+ }
197
+ continue;
198
+ }
199
+ if (option.type === 'number' && !Number.isFinite(Number(value))) {
200
+ throw new RedlineError('usage', `--${flag} must be a number, not "${value}"`);
201
+ }
202
+ if (option.values && !option.values.includes(value)) {
203
+ throw new RedlineError('usage', `--${flag} must be one of ${option.values.join(', ')}, not "${value}"`);
204
+ }
205
+ resolved[option.env] = value;
206
+ }
207
+ return resolved;
208
+ }
209
+ /** The help for one command, generated from the same table that validates it. */
210
+ export function helpFor(name, spec) {
211
+ const left = (flag, option) => ` --${flag}${option.type === 'boolean' ? '' : ` <${option.type}>`}`;
212
+ // Sized to the widest flag in THIS command, not to a constant: a fixed column
213
+ // collides the moment a longer flag is added, and a help screen whose columns
214
+ // run together reads as unmaintained.
215
+ const width = Math.max(...Object.entries(spec.options).map(([f, o]) => left(f, o).length)) + 2;
216
+ const flags = Object.entries(spec.options).map(([flag, option]) => {
217
+ const suffix = option.required
218
+ ? ' (required)'
219
+ : option.default
220
+ ? ` (default: ${option.default})`
221
+ : '';
222
+ return left(flag, option).padEnd(width) + `${option.help}${suffix}`;
223
+ });
224
+ return [
225
+ `redline ${name} — ${spec.summary}`,
226
+ '',
227
+ `Runs in: ${spec.runsIn}.`,
228
+ '',
229
+ ...flags,
230
+ '',
231
+ 'Every option can also be set as an environment variable — see the names in the docs.',
232
+ ].join('\n');
233
+ }
234
+ /**
235
+ * The same validated flags, as argv, for a runner that parses its own.
236
+ *
237
+ * `--token` is deliberately never forwarded: it is a credential, and a command
238
+ * line is visible in the process table and lands in shell history. It travels as
239
+ * an environment variable, which is where the runner reads it from anyway.
240
+ */
241
+ export function resolveArgv(spec, flags, env, name) {
242
+ const resolvedEnv = resolveEnv(spec, flags, env, name);
243
+ const argv = [];
244
+ for (const [flag, option] of Object.entries(spec.options)) {
245
+ if (option.env === 'GH_TOKEN')
246
+ continue;
247
+ const value = resolvedEnv[option.env];
248
+ if (value === undefined)
249
+ continue;
250
+ if (option.type === 'boolean')
251
+ argv.push(`--${flag}`);
252
+ else
253
+ argv.push(`--${flag}`, value);
254
+ }
255
+ return argv;
256
+ }
257
+ //# sourceMappingURL=options.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"options.js","sourceRoot":"","sources":["../../cli/metrics/options.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,72 @@
1
+ var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExtension) || function (path, preserveJsx) {
2
+ if (typeof path === "string" && /^\.\.?\//.test(path)) {
3
+ return path.replace(/\.(tsx)$|((?:\.d)?)((?:\.[^./]+?)?)\.([cm]?)ts$/i, function (m, tsx, d, ext, cm) {
4
+ return tsx ? preserveJsx ? ".jsx" : ".js" : d && (!ext || !cm) ? m : (d + ext + "." + cm.toLowerCase() + "js");
5
+ });
6
+ }
7
+ return path;
8
+ };
9
+ import { existsSync } from 'node:fs';
10
+ import { join } from 'node:path';
11
+ import { pathToFileURL } from 'node:url';
12
+ import { isRedlineError, RedlineError } from "../core/errors.js";
13
+ import { METRICS_COMMANDS, REGISTRY_COMMAND, resolveArgv, resolveEnv } from "./options.js";
14
+ export function specFor(name) {
15
+ if (name === 'registry')
16
+ return REGISTRY_COMMAND;
17
+ const spec = METRICS_COMMANDS[name];
18
+ if (!spec) {
19
+ throw new RedlineError('usage', `unknown metrics command "${name}"`, `known: ${Object.keys(METRICS_COMMANDS).join(', ')}`);
20
+ }
21
+ return spec;
22
+ }
23
+ /**
24
+ * Validate the flags, then run the runner in this process.
25
+ *
26
+ * The runner reads its configuration from the environment, which is the contract
27
+ * it already documents. This layer is its front door: it decides what is valid,
28
+ * says so in `--help`, and refuses before anything runs — rather than letting a
29
+ * missing token surface as a 401 halfway through an org walk.
30
+ */
31
+ export async function runMetrics(name, opts) {
32
+ const spec = specFor(name);
33
+ const env = opts.env ?? process.env;
34
+ const label = name === 'registry' ? 'registry' : `metrics ${name}`;
35
+ // Both paths validate through the same table. Which one a runner takes is a
36
+ // fact about that runner, never a difference in what the user may type.
37
+ const resolved = resolveEnv(spec, opts.flags, env, label);
38
+ const argv = spec.argv ? resolveArgv(spec, opts.flags, env, label) : null;
39
+ const script = join(opts.root, spec.script);
40
+ if (!existsSync(script) && !opts.load) {
41
+ throw new RedlineError('failed', `${spec.script} is missing from this installation`, 'reinstall redlinegate — the runners ship inside the package');
42
+ }
43
+ // Applied to the real environment because the runner reads process.env
44
+ // directly. Scoped to this process, which lives for exactly one command.
45
+ for (const [key, value] of Object.entries(resolved))
46
+ process.env[key] = value;
47
+ // A runner that parses its own argv reads process.argv, so it is set the way
48
+ // node would have: [execPath, script, ...flags].
49
+ const savedArgv = process.argv;
50
+ if (argv)
51
+ process.argv = [process.argv[0] ?? 'node', script, ...argv];
52
+ try {
53
+ const load = opts.load ?? ((path) => import(__rewriteRelativeImportExtension(pathToFileURL(path).href)));
54
+ await load(script);
55
+ }
56
+ catch (error) {
57
+ if (isRedlineError(error))
58
+ throw error;
59
+ // A runner that throws is reporting a real failure of the work — a bad
60
+ // credential, an unreachable host, a malformed data file. Letting it reach
61
+ // the CLI's catch-all would print "redline failed unexpectedly", which tells
62
+ // a reader the tool is broken when the token is.
63
+ const message = error instanceof Error ? error.message : String(error);
64
+ throw new RedlineError('host', `redline ${name} failed: ${message.split('\n')[0]}`, /401|403|bad credentials/i.test(message)
65
+ ? 'check the token — most of these commands need org read access'
66
+ : undefined);
67
+ }
68
+ finally {
69
+ process.argv = savedArgv;
70
+ }
71
+ }
72
+ //# sourceMappingURL=run.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"run.js","sourceRoot":"","sources":["../../cli/metrics/run.ts"],"names":[],"mappings":"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"}