gentle-pi 2.4.0 → 2.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +292 -25
- package/assets/agents/gentle-ai-worker.md +13 -0
- package/assets/agents/jd-fix-agent.md +18 -0
- package/assets/agents/jd-judge-a.md +1 -1
- package/assets/agents/jd-judge-b.md +1 -1
- package/assets/agents/sdd-apply.md +7 -5
- package/assets/agents/sdd-archive.md +5 -3
- package/assets/agents/sdd-design.md +4 -0
- package/assets/agents/sdd-explore.md +4 -0
- package/assets/agents/sdd-init.md +4 -0
- package/assets/agents/sdd-onboard.md +4 -0
- package/assets/agents/sdd-proposal.md +4 -0
- package/assets/agents/sdd-remediate.md +37 -0
- package/assets/agents/sdd-research.md +26 -3
- package/assets/agents/sdd-spec.md +4 -0
- package/assets/agents/sdd-status.md +9 -75
- package/assets/agents/sdd-sync.md +4 -0
- package/assets/agents/sdd-tasks.md +4 -0
- package/assets/agents/sdd-verify.md +5 -3
- package/assets/chains/sdd-full.chain.md +4 -0
- package/assets/chains/sdd-plan.chain.md +4 -0
- package/assets/chains/sdd-verify.chain.md +4 -0
- package/assets/migrations/managed-assets-v2.5.0.json +7 -0
- package/assets/orchestrator-delegation.md +39 -11
- package/assets/orchestrator.md +5 -5
- package/assets/sdd-orchestrator-workflow.md +54 -21
- package/assets/support/sdd-status-contract.md +34 -90
- package/contracts/telemetry/runtime-aggregate-v1.schema.json +65 -0
- package/docs/delegated-verification.md +25 -0
- package/docs/telemetry.md +94 -0
- package/docs/windows-startup-console-visibility.md +18 -0
- package/extensions/ask-user-choice.ts +159 -25
- package/extensions/codegraph-tools.ts +95 -5
- package/extensions/gentle-agents.ts +1337 -0
- package/extensions/gentle-ai.ts +2916 -386
- package/extensions/gentle-shell.ts +650 -0
- package/extensions/gentle-todo.ts +234 -0
- package/extensions/quiet-tools.ts +2 -1
- package/extensions/runtime-metrics.ts +130 -0
- package/extensions/sdd-init.ts +2 -2
- package/extensions/startup-banner.ts +52 -75
- package/lib/agent-profiles.ts +550 -0
- package/lib/agents-completion-delivery.ts +72 -0
- package/lib/agents-config.ts +315 -0
- package/lib/agents-history.ts +88 -0
- package/lib/agents-messaging.ts +187 -0
- package/lib/agents-protocol.ts +501 -0
- package/lib/agents-runner.ts +1012 -0
- package/lib/agents-thread-view.ts +57 -0
- package/lib/agents-transcript.ts +87 -0
- package/lib/agents-view-layout.ts +40 -0
- package/lib/agents-view.ts +914 -0
- package/lib/agents-widget.ts +241 -0
- package/lib/gentle-ai-binary.ts +3 -1
- package/lib/gentle-ai-renderer.ts +143 -25
- package/lib/native-choice-list.ts +194 -0
- package/lib/native-fullscreen-interaction.ts +47 -0
- package/lib/native-pointer-region.ts +164 -0
- package/lib/native-review-cli.ts +371 -13
- package/lib/orchestrator-presence.ts +337 -0
- package/lib/profiles-orchestrator.ts +203 -0
- package/lib/review-candidate-view-owner.ts +427 -0
- package/lib/review-candidate-view.ts +150 -48
- package/lib/review-consent-component.ts +247 -0
- package/lib/review-consent-ui.ts +110 -0
- package/lib/review-host-relay.ts +28 -0
- package/lib/review-integration-v2.ts +243 -11
- package/lib/review-last-event-controller.ts +8 -4
- package/lib/review-relay-contract.ts +11 -0
- package/lib/review-reminder-receipt.ts +74 -0
- package/lib/review-repository.ts +2 -2
- package/lib/review-risk-assessment.ts +339 -0
- package/lib/review-session-standing-permission-ipc.ts +309 -0
- package/lib/review-session-standing-permission.ts +240 -0
- package/lib/runtime-metrics-children.ts +199 -0
- package/lib/runtime-metrics-delivery.ts +68 -0
- package/lib/runtime-metrics-native.ts +166 -0
- package/lib/runtime-metrics-pi-identity.ts +113 -0
- package/lib/runtime-metrics-policy.ts +51 -0
- package/lib/runtime-metrics.ts +255 -0
- package/lib/sdd-preflight.ts +362 -81
- package/lib/sdd-research-capabilities.ts +228 -0
- package/lib/sdd-status.ts +29 -7
- package/lib/session-worktree-registry.ts +118 -0
- package/lib/shell-bar.ts +184 -0
- package/lib/shell-card.ts +133 -0
- package/lib/shell-changes-view.ts +530 -0
- package/lib/shell-changes.ts +290 -0
- package/lib/shell-gauge.ts +40 -0
- package/lib/shell-prompt.ts +115 -0
- package/lib/shell-sidebar-banner.ts +11 -0
- package/lib/shell-sidebar-layout.ts +213 -0
- package/lib/shell-sidebar.ts +41 -0
- package/lib/shell-todo.ts +297 -0
- package/lib/shell-usage-view.ts +76 -0
- package/lib/shell-usage.ts +246 -0
- package/lib/telemetry-trigger.ts +153 -0
- package/package.json +8 -5
- package/runtime/gentle-ai-binary.mjs +3 -1
- package/runtime/native-review-cli.mjs +370 -12
- package/runtime/review-integration-v2.mjs +243 -11
- package/runtime/review-relay-contract.mjs +11 -0
- package/runtime/review-risk-assessment.mjs +340 -0
- package/runtime/telemetry-trigger.mjs +154 -0
- package/scripts/build-runtime-modules.mjs +11 -1
- package/scripts/check-types.mjs +125 -0
- package/scripts/gentle-ai-installer.mjs +10 -10
- package/scripts/install-gentle-ai.mjs +12 -0
- package/scripts/install-tui-mode-setting.mjs +114 -0
- package/scripts/test-packed-runner.mjs +38 -2
- package/scripts/types-baseline.json +99 -0
- package/scripts/verify-package-files.mjs +8 -2
- package/skills/_shared/review-ledger-contract.md +20 -2
- package/skills/issue-creation/SKILL.md +3 -3
- package/skills/judgment-day/SKILL.md +17 -3
- package/skills/judgment-day/references/prompts-and-formats.md +14 -3
- package/tests/agent-profiles.test.ts +722 -0
- package/tests/agents-completion-delivery.test.ts +94 -0
- package/tests/agents-config.test.ts +205 -0
- package/tests/agents-fake-child.ts +66 -0
- package/tests/agents-grouping.test.ts +179 -0
- package/tests/agents-history.test.ts +54 -0
- package/tests/agents-integration.test.ts +100 -0
- package/tests/agents-messaging.test.ts +94 -0
- package/tests/agents-protocol.test.ts +198 -0
- package/tests/agents-queries.test.ts +190 -0
- package/tests/agents-responsive.test.ts +43 -0
- package/tests/agents-runner-process.test.ts +111 -0
- package/tests/agents-runner.test.ts +959 -0
- package/tests/agents-thread-view.test.ts +45 -0
- package/tests/agents-transcript.test.ts +30 -0
- package/tests/agents-view.test.ts +685 -0
- package/tests/agents-widget.test.ts +141 -0
- package/tests/artifact-language.test.ts +25 -2
- package/tests/ask-user-choice.test.ts +325 -5
- package/tests/asset-installation-runtime.test.ts +108 -0
- package/tests/autonomous-guard.test.ts +116 -1
- package/tests/codegraph-tools.test.ts +112 -2
- package/tests/delegated-key-learnings-contract.test.ts +1 -1
- package/tests/devbinary/native-review-parity.devtest.ts +110 -0
- package/tests/feature-request-form.test.ts +67 -0
- package/tests/fixtures/agents-messaging-child.mjs +5 -0
- package/tests/fixtures/agents-process-child.mjs +23 -0
- package/tests/fixtures/runtime-metrics-native-batches.json +6 -0
- package/tests/gentle-agents.test.ts +2168 -0
- package/tests/gentle-ai-binary.test.ts +7 -2
- package/tests/gentle-ai-installer.test.ts +47 -47
- package/tests/gentle-ai-renderer.test.ts +103 -0
- package/tests/gentle-ai.test.ts +971 -15
- package/tests/gentle-card-text.ts +35 -0
- package/tests/gentle-shell.test.ts +818 -0
- package/tests/gentle-todo.test.ts +226 -0
- package/tests/install-tui-mode-setting.test.ts +324 -0
- package/tests/issue-creation-skill.test.ts +22 -0
- package/tests/model-routing-authority.test.ts +12 -0
- package/tests/native-choice-list.test.ts +202 -0
- package/tests/native-fullscreen-interaction.test.ts +125 -0
- package/tests/native-pointer-region.test.ts +245 -0
- package/tests/native-review-capability-contract.test.ts +27 -1
- package/tests/native-review-cli.test.ts +317 -3
- package/tests/native-review-consent.test.ts +91 -0
- package/tests/native-review-parity-runtime.test.ts +8 -2
- package/tests/native-review-parity.test.ts +43 -29
- package/tests/native-sdd-attempt-authority.test.ts +7 -2
- package/tests/orchestrator-budget.test.ts +69 -0
- package/tests/orchestrator-presence.test.ts +389 -0
- package/tests/orchestrator-rdd-ownership.test.ts +9 -0
- package/tests/package-manifest.test.ts +243 -7
- package/tests/profiles-orchestrator.test.ts +208 -0
- package/tests/quiet-tool-rendering.test.ts +97 -37
- package/tests/rdd-aware-verification-contract.test.ts +226 -0
- package/tests/rdd-status-line.test.ts +286 -0
- package/tests/review-agent-end-preflight.test.ts +332 -24
- package/tests/review-candidate-view.test.ts +751 -7
- package/tests/review-consent-ui.test.ts +352 -0
- package/tests/review-contract-prompt.test.ts +17 -0
- package/tests/review-controller-native-recovery.test.ts +29 -4
- package/tests/review-controller-native-routing.test.ts +884 -7
- package/tests/review-controller-workspace-root.test.ts +45 -2
- package/tests/review-controller.test.ts +26 -1
- package/tests/review-host-relay-restart-parity.test.ts +142 -1
- package/tests/review-host-relay-routing.test.ts +384 -8
- package/tests/review-host-relay.test.ts +29 -0
- package/tests/review-integration-v2-forward.test.ts +44 -0
- package/tests/review-integration-v2.test.ts +276 -0
- package/tests/review-last-event-closure.test.ts +112 -3
- package/tests/review-ledger-contract.test.ts +61 -6
- package/tests/review-relay-contract.test.ts +26 -0
- package/tests/review-reminder-receipt.test.ts +62 -0
- package/tests/review-repository.test.ts +28 -1
- package/tests/review-risk-assessment.test.ts +626 -0
- package/tests/review-session-standing-permission-controller.test.ts +656 -0
- package/tests/review-session-standing-permission-ipc.test.ts +233 -0
- package/tests/review-session-standing-permission-runtime.test.ts +212 -0
- package/tests/review-session-standing-permission.test.ts +156 -0
- package/tests/runtime-harness.mjs +447 -39
- package/tests/runtime-metrics-children.test.ts +206 -0
- package/tests/runtime-metrics-delivery.test.ts +85 -0
- package/tests/runtime-metrics-extension.test.ts +187 -0
- package/tests/runtime-metrics-native.test.ts +209 -0
- package/tests/runtime-metrics-pi-identity.test.ts +113 -0
- package/tests/runtime-metrics-policy.test.ts +62 -0
- package/tests/runtime-metrics.test.ts +184 -0
- package/tests/sdd-agent-tools.test.ts +10 -1
- package/tests/sdd-execution-routing-contract.test.ts +28 -0
- package/tests/sdd-managed-runtime-settlement.test.ts +331 -0
- package/tests/sdd-native-managed-uptake.test.ts +253 -0
- package/tests/sdd-planning-routing-contract.test.ts +45 -0
- package/tests/sdd-preflight.test.ts +252 -8
- package/tests/sdd-research-capabilities.test.ts +256 -0
- package/tests/sdd-research-live.test.ts +241 -0
- package/tests/sdd-selection-transport.test.ts +504 -0
- package/tests/sdd-status.test.ts +51 -0
- package/tests/session-worktree-registry.test.ts +135 -0
- package/tests/shell-bar.test.ts +176 -0
- package/tests/shell-card.test.ts +139 -0
- package/tests/shell-changes-view.test.ts +609 -0
- package/tests/shell-changes.test.ts +350 -0
- package/tests/shell-prompt.test.ts +140 -0
- package/tests/shell-sidebar-banner.test.ts +23 -0
- package/tests/shell-sidebar-layout.test.ts +387 -0
- package/tests/shell-sidebar.test.ts +50 -0
- package/tests/shell-todo.test.ts +259 -0
- package/tests/shell-usage-view.test.ts +62 -0
- package/tests/shell-usage.test.ts +197 -0
- package/tests/startup-banner.test.ts +126 -0
- package/tests/telemetry-trigger.test.ts +351 -0
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
// Generated by scripts/build-runtime-modules.mjs. Do not edit.
|
|
2
|
+
// gentle-pi#662: pure decoding, tier mapping, and verification-plan logic for
|
|
3
|
+
// the native read-only `gentle-ai review assess` command (gentle-ai#4295).
|
|
4
|
+
// Kept independent of the native review CLI wrapper (`lib/native-review-cli.ts`)
|
|
5
|
+
// so the decoder and the verificationPlan table can be unit-tested without a
|
|
6
|
+
// child process, and so the parent tool and the delegation asset agree on one
|
|
7
|
+
// deterministic mapping.
|
|
8
|
+
//
|
|
9
|
+
// Native reference (gentle-ai#4295, landing in parallel with this change):
|
|
10
|
+
// `gentle-ai review assess --cwd <repo> [--base-ref <ref> --committed-only] --json`
|
|
11
|
+
// prints `{"schema":"gentle-ai.review-assessment/v1","risk":"passive|medium|high",
|
|
12
|
+
// "reasons":[{"code":"…","path":"…","detail":"…"}],"changed_paths":1,
|
|
13
|
+
// "changed_lines":1,"candidate":{"kind":"current-changes|base-diff","base_ref":"…"}}`.
|
|
14
|
+
// A non-zero exit or a failure envelope means the candidate could not be
|
|
15
|
+
// assessed; hosts treat that as `high`. Older binaries without the verb (or
|
|
16
|
+
// any other decode/process failure) fail closed the same way -- see
|
|
17
|
+
// `verificationTierForUnassessable` below.
|
|
18
|
+
|
|
19
|
+
export const REVIEW_ASSESSMENT_SCHEMA = "gentle-ai.review-assessment/v1" ;
|
|
20
|
+
|
|
21
|
+
export const REVIEW_ASSESSMENT_RISK = {
|
|
22
|
+
PASSIVE: "passive",
|
|
23
|
+
MEDIUM: "medium",
|
|
24
|
+
HIGH: "high",
|
|
25
|
+
} ;
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
export const REVIEW_ASSESSMENT_CANDIDATE_KIND = {
|
|
29
|
+
CURRENT_CHANGES: "current-changes",
|
|
30
|
+
BASE_DIFF: "base-diff",
|
|
31
|
+
} ;
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
function isRecord(value ) {
|
|
55
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function decodeReason(value ) {
|
|
59
|
+
if (!isRecord(value)) throw new TypeError("review assessment reason must be an object");
|
|
60
|
+
const { code, path, detail } = value;
|
|
61
|
+
if (typeof code !== "string" || code.length === 0) throw new TypeError("review assessment reason.code must be a non-empty string");
|
|
62
|
+
if (typeof path !== "string") throw new TypeError("review assessment reason.path must be a string");
|
|
63
|
+
if (typeof detail !== "string") throw new TypeError("review assessment reason.detail must be a string");
|
|
64
|
+
return Object.freeze({ code, path, detail });
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function decodeCandidate(value ) {
|
|
68
|
+
if (!isRecord(value)) throw new TypeError("review assessment candidate must be an object");
|
|
69
|
+
const { kind, base_ref: baseRef } = value;
|
|
70
|
+
if (kind !== REVIEW_ASSESSMENT_CANDIDATE_KIND.CURRENT_CHANGES && kind !== REVIEW_ASSESSMENT_CANDIDATE_KIND.BASE_DIFF) {
|
|
71
|
+
throw new TypeError("review assessment candidate.kind must be current-changes or base-diff");
|
|
72
|
+
}
|
|
73
|
+
if (baseRef !== undefined && (typeof baseRef !== "string" || baseRef.length === 0)) {
|
|
74
|
+
throw new TypeError("review assessment candidate.base_ref must be a non-empty string when present");
|
|
75
|
+
}
|
|
76
|
+
return Object.freeze({ kind, baseRef: baseRef });
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Decodes and validates one `gentle-ai.review-assessment/v1` envelope. Rejects
|
|
81
|
+
* any other schema, an unrecognized risk value, or a malformed shape -- the
|
|
82
|
+
* native review CLI wrapper turns a thrown error here into the same
|
|
83
|
+
* schema-incompatible failure it already produces for every other decoded
|
|
84
|
+
* native response.
|
|
85
|
+
*/
|
|
86
|
+
export function decodeReviewAssessmentV1(value ) {
|
|
87
|
+
if (!isRecord(value)) throw new TypeError("review assessment must be an object");
|
|
88
|
+
if (value.schema !== REVIEW_ASSESSMENT_SCHEMA) throw new TypeError(`review assessment schema must be ${REVIEW_ASSESSMENT_SCHEMA}`);
|
|
89
|
+
const { risk, reasons, changed_paths: changedPaths, changed_lines: changedLines, candidate } = value;
|
|
90
|
+
if (risk !== REVIEW_ASSESSMENT_RISK.PASSIVE && risk !== REVIEW_ASSESSMENT_RISK.MEDIUM && risk !== REVIEW_ASSESSMENT_RISK.HIGH) {
|
|
91
|
+
throw new TypeError("review assessment risk must be passive, medium, or high");
|
|
92
|
+
}
|
|
93
|
+
if (!Array.isArray(reasons)) throw new TypeError("review assessment reasons must be an array");
|
|
94
|
+
if (!Number.isSafeInteger(changedPaths) || (changedPaths ) < 0) throw new TypeError("review assessment changed_paths must be a non-negative integer");
|
|
95
|
+
if (!Number.isSafeInteger(changedLines) || (changedLines ) < 0) throw new TypeError("review assessment changed_lines must be a non-negative integer");
|
|
96
|
+
return Object.freeze({
|
|
97
|
+
schema: REVIEW_ASSESSMENT_SCHEMA,
|
|
98
|
+
risk,
|
|
99
|
+
reasons: Object.freeze(reasons.map(decodeReason)),
|
|
100
|
+
changedPaths: changedPaths ,
|
|
101
|
+
changedLines: changedLines ,
|
|
102
|
+
candidate: decodeCandidate(candidate),
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// ---------------------------------------------------------------------------
|
|
107
|
+
// Verification-plan tiering (gentle-pi#662).
|
|
108
|
+
// ---------------------------------------------------------------------------
|
|
109
|
+
|
|
110
|
+
export const VERIFICATION_TIER = {
|
|
111
|
+
PASSIVE: "passive",
|
|
112
|
+
MEDIUM: "medium",
|
|
113
|
+
HIGH: "high",
|
|
114
|
+
UNASSESSABLE: "unassessable",
|
|
115
|
+
} ;
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
export const RDD_LINE = {
|
|
119
|
+
ON: "on",
|
|
120
|
+
OFF: "off",
|
|
121
|
+
UNKNOWN: "unknown",
|
|
122
|
+
} ;
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
export const WRITER_PROFILE = {
|
|
126
|
+
SMALL: "small",
|
|
127
|
+
LARGE: "large",
|
|
128
|
+
} ;
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
// gentle-pi#668: whether the native review actually reached a terminal
|
|
132
|
+
// outcome for the current candidate. The `on` branch of `verificationPlan`
|
|
133
|
+
// (the writer self-verifies, the native review is the independent check, no
|
|
134
|
+
// separate verifier) holds only for `CLOSED` -- a human decline for this
|
|
135
|
+
// candidate, a clone-local RDD disable, a refused START/STATUS, or any other
|
|
136
|
+
// non-terminal outcome falls back to the exact same risk-gated path as `off`.
|
|
137
|
+
// `UNKNOWN` is the fail-closed default for an omitted outcome: never treated
|
|
138
|
+
// as `CLOSED`.
|
|
139
|
+
export const NATIVE_REVIEW_OUTCOME = {
|
|
140
|
+
CLOSED: "closed",
|
|
141
|
+
DECLINED: "declined",
|
|
142
|
+
UNAVAILABLE: "unavailable",
|
|
143
|
+
UNKNOWN: "unknown",
|
|
144
|
+
} ;
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Builds the risk-gated verification plan shared by the `off`/`unknown` RDD
|
|
168
|
+
* lines and by an `on` line whose native review did not close for this
|
|
169
|
+
* candidate (gentle-pi#668). `gatePrefix` supplies the sentence fragment
|
|
170
|
+
* naming which branch fired, up to and including the word "and"; the
|
|
171
|
+
* tier-specific clause is appended after it.
|
|
172
|
+
*/
|
|
173
|
+
function riskGatedPlan(gatePrefix , risk , writerProfile ) {
|
|
174
|
+
if (risk === VERIFICATION_TIER.PASSIVE) {
|
|
175
|
+
return Object.freeze({
|
|
176
|
+
writerSelfVerification: false,
|
|
177
|
+
structuralReadbackOnly: true,
|
|
178
|
+
independentVerifier: false,
|
|
179
|
+
reason: `${gatePrefix} the native assessment reports passive risk: a structural readback by the parent is the complete check, with no separate verifier and no tests.`,
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
if (risk === VERIFICATION_TIER.MEDIUM) {
|
|
184
|
+
const smallModelBias = writerProfile === WRITER_PROFILE.SMALL;
|
|
185
|
+
return Object.freeze({
|
|
186
|
+
writerSelfVerification: true,
|
|
187
|
+
structuralReadbackOnly: false,
|
|
188
|
+
independentVerifier: smallModelBias,
|
|
189
|
+
reason: smallModelBias
|
|
190
|
+
? `${gatePrefix} the native assessment reports medium risk, but the small-model bias raises it one tier to high for verification purposes: the writer self-verifies and a separate independent verifier also runs.`
|
|
191
|
+
: `${gatePrefix} the native assessment reports medium risk with a large writer profile: the writer's self-verification stands and no separate verifier is required.`,
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// high or unassessable.
|
|
196
|
+
return Object.freeze({
|
|
197
|
+
writerSelfVerification: true,
|
|
198
|
+
structuralReadbackOnly: false,
|
|
199
|
+
independentVerifier: true,
|
|
200
|
+
reason: risk === VERIFICATION_TIER.UNASSESSABLE
|
|
201
|
+
? `${gatePrefix} the native risk assessment could not be produced, so the candidate is treated as high risk: the writer self-verifies and a separate independent verifier always runs.`
|
|
202
|
+
: `${gatePrefix} the native assessment reports high risk: the writer self-verifies and a separate independent verifier always runs.`,
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Names why an `on` line falls back to the risk-gated path, for every
|
|
207
|
+
// non-closed native review outcome (gentle-pi#668). `UNKNOWN` covers both an
|
|
208
|
+
// omitted outcome and a caller-supplied one this decoder does not
|
|
209
|
+
// recognize -- both fail closed the same way.
|
|
210
|
+
function nonClosedOutcomeClause(outcome ) {
|
|
211
|
+
switch (outcome) {
|
|
212
|
+
case NATIVE_REVIEW_OUTCOME.DECLINED:
|
|
213
|
+
return "the native review was declined for this candidate";
|
|
214
|
+
case NATIVE_REVIEW_OUTCOME.UNAVAILABLE:
|
|
215
|
+
return "the native review is unavailable for this candidate";
|
|
216
|
+
default:
|
|
217
|
+
return "the native review outcome for this candidate is unknown";
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Computes who verifies a delegated writer's change, exactly as
|
|
223
|
+
* gentle-pi#662/#668 specify:
|
|
224
|
+
*
|
|
225
|
+
* - `rdd: "on"` AND the native review reached `NATIVE_REVIEW_OUTCOME.CLOSED`
|
|
226
|
+
* for this candidate: the writer self-verifies and no independent verifier
|
|
227
|
+
* runs, because the closed native review is the check -- except `passive`,
|
|
228
|
+
* which gets a structural readback by the parent instead.
|
|
229
|
+
* - `rdd: "on"` with any other outcome (`declined`, `unavailable`, or the
|
|
230
|
+
* fail-closed `unknown` default for an omitted/unrecognized outcome): the
|
|
231
|
+
* `on` branch never held for this candidate, so the risk-gated path below
|
|
232
|
+
* applies exactly as `off` -- declining a review is candidate-scoped and
|
|
233
|
+
* never lowers the bar below the RDD-off path.
|
|
234
|
+
* - `rdd: "off"` or `"unknown"` (both gate identically, so an unknown RDD line
|
|
235
|
+
* never lowers a tier relative to `off`; `nativeReviewOutcome` is ignored
|
|
236
|
+
* entirely on these lines):
|
|
237
|
+
* - `passive`: structural readback by the parent only; no verifier, no
|
|
238
|
+
* tests.
|
|
239
|
+
* - `medium`: the writer self-verifies; a separate independent verifier
|
|
240
|
+
* runs only when `writerProfile` is `"small"` -- the small-model bias
|
|
241
|
+
* raises the tier by one for verification purposes (medium -> high). An
|
|
242
|
+
* unknown or omitted writer profile resolves to `"small"` before it
|
|
243
|
+
* reaches this function (see `resolveWriterProfile`), so an unrecognized
|
|
244
|
+
* profile never lowers a tier below what a known small model would get.
|
|
245
|
+
* - `high` or `unassessable` (a failed or unrecognized native assessment is
|
|
246
|
+
* always treated as high): the writer self-verifies and an independent
|
|
247
|
+
* verifier always runs.
|
|
248
|
+
*/
|
|
249
|
+
export function verificationPlan(input ) {
|
|
250
|
+
const { rddLine, risk, writerProfile } = input;
|
|
251
|
+
|
|
252
|
+
if (rddLine === RDD_LINE.ON) {
|
|
253
|
+
const outcome = input.nativeReviewOutcome ?? NATIVE_REVIEW_OUTCOME.UNKNOWN;
|
|
254
|
+
if (outcome === NATIVE_REVIEW_OUTCOME.CLOSED) {
|
|
255
|
+
if (risk === VERIFICATION_TIER.PASSIVE) {
|
|
256
|
+
return Object.freeze({
|
|
257
|
+
writerSelfVerification: false,
|
|
258
|
+
structuralReadbackOnly: true,
|
|
259
|
+
independentVerifier: false,
|
|
260
|
+
reason: "receipt-driven development is on and the native review closed for this candidate; the assessment also reports passive risk: a structural readback by the parent replaces both the writer's self-verification and any separate verifier.",
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
return Object.freeze({
|
|
264
|
+
writerSelfVerification: true,
|
|
265
|
+
structuralReadbackOnly: false,
|
|
266
|
+
independentVerifier: false,
|
|
267
|
+
reason: "receipt-driven development is on and the native review closed for this candidate: the writer's self-verification is the record and the closed native review was the independent check the writer cannot influence, so no separate verifier is required.",
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
// The on branch holds only while the native review reaches a terminal
|
|
271
|
+
// outcome for this candidate. Anything else -- declined, unavailable, or
|
|
272
|
+
// unknown -- falls back to the exact same risk-gated path as off.
|
|
273
|
+
return riskGatedPlan(
|
|
274
|
+
`receipt-driven development is on, but ${nonClosedOutcomeClause(outcome)}, so the risk-gated path applies exactly as receipt-driven development off, and`,
|
|
275
|
+
risk,
|
|
276
|
+
writerProfile,
|
|
277
|
+
);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// rddLine is "off" or "unknown" here -- both gate on the native risk
|
|
281
|
+
// assessment identically, so an unknown RDD line never lowers a tier, and
|
|
282
|
+
// neither line ever consults nativeReviewOutcome.
|
|
283
|
+
return riskGatedPlan(`receipt-driven development is ${rddLine} and`, risk, writerProfile);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// ---------------------------------------------------------------------------
|
|
287
|
+
// Small-writer-profile predicate (gentle-pi#662).
|
|
288
|
+
// ---------------------------------------------------------------------------
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* The minimal shape of `lib/agents-config.ts`'s `ResolvedProfile` this
|
|
292
|
+
* predicate needs: the resolved model reference and its resolved effort
|
|
293
|
+
* (`ResolvedProfile.thinking`, one of `ThinkingLevel`).
|
|
294
|
+
*/
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
// Matches `mini` only as a whole token of the model id -- delimited by the
|
|
301
|
+
// string start/end or one of `-_./:` or whitespace -- never a bare substring.
|
|
302
|
+
// `gpt-5.4-mini`, `o4-mini`, `openai/gpt-5.4-mini`, `claude-mini`, and
|
|
303
|
+
// `mini-high` all match; `gemini`, `gemini-2.5-pro`, and `gemini-2.5-flash`
|
|
304
|
+
// do not, because their `mini` is preceded by `ge`, not a delimiter.
|
|
305
|
+
const SMALL_MODEL_ID_TOKEN = /(?:^|[-_./:\s])mini(?:$|[-_./:\s])/i;
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* A writer profile counts as a "small model" for the medium-risk bias when:
|
|
309
|
+
* - its resolved effort is `low`, or
|
|
310
|
+
* - its resolved model id carries `mini` as a whole token (see
|
|
311
|
+
* `SMALL_MODEL_ID_TOKEN`), or
|
|
312
|
+
* - it carries NO signal at all -- no profile object, or a profile with
|
|
313
|
+
* neither a model id nor a resolved effort. An unknown or omitted writer
|
|
314
|
+
* profile fails closed to small rather than defaulting to large, so a
|
|
315
|
+
* caller that cannot resolve the writer's profile never gets a weaker
|
|
316
|
+
* verification plan than a known small model would (gentle-pi#662).
|
|
317
|
+
*
|
|
318
|
+
* A profile that carries a model id or an effort, just not a matching one
|
|
319
|
+
* (for example `{model: {id: "claude-sonnet-5"}, thinking: "high"}`), is
|
|
320
|
+
* "known large" and returns `false` -- fail-closed applies only to a truly
|
|
321
|
+
* unknown/omitted profile, not to an explicitly resolved large one.
|
|
322
|
+
*/
|
|
323
|
+
export function isSmallWriterProfile(profile ) {
|
|
324
|
+
if (profile === undefined) return true;
|
|
325
|
+
if (profile.thinking === "low") return true;
|
|
326
|
+
const modelId = profile.model?.id;
|
|
327
|
+
if (typeof modelId === "string") return SMALL_MODEL_ID_TOKEN.test(modelId);
|
|
328
|
+
// No model id was resolved. Fail closed to small only when effort is also
|
|
329
|
+
// unknown -- there is no signal at all to call this a known large model.
|
|
330
|
+
return profile.thinking === undefined;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
/**
|
|
334
|
+
* Maps a resolved writer profile to the `writerProfile` input `verificationPlan`
|
|
335
|
+
* expects. An unknown or omitted profile never lowers a tier: it resolves to
|
|
336
|
+
* `"small"`, exactly like `isSmallWriterProfile` (gentle-pi#662).
|
|
337
|
+
*/
|
|
338
|
+
export function resolveWriterProfile(profile ) {
|
|
339
|
+
return isSmallWriterProfile(profile) ? WRITER_PROFILE.SMALL : WRITER_PROFILE.LARGE;
|
|
340
|
+
}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
// Generated by scripts/build-runtime-modules.mjs. Do not edit.
|
|
2
|
+
import { spawn as nodeSpawn } from "node:child_process";
|
|
3
|
+
|
|
4
|
+
// gentle-ai#4309 owns anonymous usage telemetry end to end: install and
|
|
5
|
+
// heartbeat events, the notice, every kill switch, and the wire contract.
|
|
6
|
+
// gentle-pi#677 only nudges the local binary once per process, best-effort,
|
|
7
|
+
// and never surfaces the outcome to the user. This module is the pure,
|
|
8
|
+
// testable core of that nudge (`shouldTriggerTelemetry`,
|
|
9
|
+
// `spawnTelemetryTrigger`) plus the decode helper the foreground
|
|
10
|
+
// `/gentle:telemetry` slash command uses to relay `<op> --json` output
|
|
11
|
+
// (`decodeTelemetryTriggerDecision`).
|
|
12
|
+
|
|
13
|
+
export const TELEMETRY_TRIGGER_KILL_TIMEOUT_MS = 3_000;
|
|
14
|
+
|
|
15
|
+
export const TELEMETRY_TRIGGER_CONTRACT = "gentle-ai.telemetry-trigger/v1";
|
|
16
|
+
|
|
17
|
+
export const TELEMETRY_TRIGGER_DECISIONS = [
|
|
18
|
+
"enrolled",
|
|
19
|
+
"sent_install",
|
|
20
|
+
"sent_heartbeat",
|
|
21
|
+
"rate_limited",
|
|
22
|
+
"backoff",
|
|
23
|
+
"disabled",
|
|
24
|
+
] ;
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Decodes `gentle-ai telemetry trigger --json` stdout. An older binary
|
|
35
|
+
* without the verb prints `unknown telemetry command` and exits non-zero, so
|
|
36
|
+
* its stdout never parses as JSON here; that -- along with malformed JSON, a
|
|
37
|
+
* mismatched schema, an unrecognized decision, or a missing/non-string
|
|
38
|
+
* source -- means "nothing to do" and returns undefined rather than
|
|
39
|
+
* throwing.
|
|
40
|
+
*/
|
|
41
|
+
export function decodeTelemetryTriggerDecision(stdout ) {
|
|
42
|
+
let parsed ;
|
|
43
|
+
try {
|
|
44
|
+
parsed = JSON.parse(stdout);
|
|
45
|
+
} catch {
|
|
46
|
+
return undefined;
|
|
47
|
+
}
|
|
48
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) return undefined;
|
|
49
|
+
const record = parsed ;
|
|
50
|
+
if (record.schema !== TELEMETRY_TRIGGER_CONTRACT) return undefined;
|
|
51
|
+
if (typeof record.decision !== "string") return undefined;
|
|
52
|
+
if (!(TELEMETRY_TRIGGER_DECISIONS ).includes(record.decision)) return undefined;
|
|
53
|
+
if (typeof record.source !== "string" || record.source.length === 0) return undefined;
|
|
54
|
+
return {
|
|
55
|
+
schema: TELEMETRY_TRIGGER_CONTRACT,
|
|
56
|
+
decision: record.decision ,
|
|
57
|
+
source: record.source,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
function telemetryKillSwitchReason(env ) {
|
|
64
|
+
if (env.DO_NOT_TRACK === "1") return "do-not-track";
|
|
65
|
+
if (env.GENTLE_AI_TELEMETRY === "0") return "telemetry-disabled";
|
|
66
|
+
if (env.CI === "true") return "ci";
|
|
67
|
+
return undefined;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* True unless one of Gentle Pi's own kill switches is set
|
|
72
|
+
* (`DO_NOT_TRACK=1`, `GENTLE_AI_TELEMETRY=0`, or `CI=true`). The binary
|
|
73
|
+
* enforces its own opt-out and rate limiting independently; this predicate
|
|
74
|
+
* only decides whether Gentle Pi spawns the trigger at all.
|
|
75
|
+
*/
|
|
76
|
+
export function shouldTriggerTelemetry(env ) {
|
|
77
|
+
return telemetryKillSwitchReason(env) === undefined;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// A minimal structural subset of node:child_process's ChildProcess, mirroring
|
|
81
|
+
// the ChildLike/Spawn seam already used by lib/agents-runner.ts: narrow
|
|
82
|
+
// enough that tests can stub it without touching a real process.
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Nudges `<executable> telemetry trigger --json`, detached and
|
|
119
|
+
* fire-and-forget. Never throws, never awaits the child's exit, and never
|
|
120
|
+
* blocks session start: a missing binary, an old binary without the verb, or
|
|
121
|
+
* a spawn failure all just report `{ spawned: false, ... }`. A 3 s timer
|
|
122
|
+
* kills a runaway child; the timer is unref'd so it can never keep the host
|
|
123
|
+
* process alive on its own, matching the detached child's own `unref()`.
|
|
124
|
+
*/
|
|
125
|
+
export function spawnTelemetryTrigger(options ) {
|
|
126
|
+
const killSwitch = telemetryKillSwitchReason(options.env);
|
|
127
|
+
if (killSwitch !== undefined) return { spawned: false, reason: killSwitch };
|
|
128
|
+
const spawnImpl = options.spawn ?? (nodeSpawn );
|
|
129
|
+
try {
|
|
130
|
+
const child = spawnImpl(options.executable, ["telemetry", "trigger", "--json"], {
|
|
131
|
+
cwd: options.cwd,
|
|
132
|
+
env: options.env,
|
|
133
|
+
detached: true,
|
|
134
|
+
windowsHide: true,
|
|
135
|
+
stdio: "ignore",
|
|
136
|
+
});
|
|
137
|
+
child.on("error", () => {
|
|
138
|
+
// Fire-and-forget: a spawn-time or runtime child error never surfaces
|
|
139
|
+
// to the caller and never affects session start.
|
|
140
|
+
});
|
|
141
|
+
const killTimer = setTimeout(() => {
|
|
142
|
+
try {
|
|
143
|
+
child.kill();
|
|
144
|
+
} catch {
|
|
145
|
+
// Best effort only; the child may already have exited.
|
|
146
|
+
}
|
|
147
|
+
}, TELEMETRY_TRIGGER_KILL_TIMEOUT_MS);
|
|
148
|
+
killTimer.unref?.();
|
|
149
|
+
child.unref();
|
|
150
|
+
return { spawned: true, reason: "spawned" };
|
|
151
|
+
} catch {
|
|
152
|
+
return { spawned: false, reason: "spawn-error" };
|
|
153
|
+
}
|
|
154
|
+
}
|
|
@@ -10,7 +10,9 @@ const sources = [
|
|
|
10
10
|
"gentle-ai-binary",
|
|
11
11
|
"review-relay-contract",
|
|
12
12
|
"review-integration-v2",
|
|
13
|
+
"review-risk-assessment",
|
|
13
14
|
"native-review-cli",
|
|
15
|
+
"telemetry-trigger",
|
|
14
16
|
];
|
|
15
17
|
const header = "// Generated by scripts/build-runtime-modules.mjs. Do not edit.\n";
|
|
16
18
|
|
|
@@ -36,6 +38,14 @@ async function main() {
|
|
|
36
38
|
if ((mode !== "--write" && mode !== "--check") || process.argv.length !== 3) {
|
|
37
39
|
throw new Error("usage: build-runtime-modules.mjs --write|--check");
|
|
38
40
|
}
|
|
41
|
+
// Pi loads these directly as TypeScript. Check syntax without generating
|
|
42
|
+
// another runtime copy or expanding the published module boundary.
|
|
43
|
+
for (const path of ["lib/runtime-metrics-delivery.ts", "lib/runtime-metrics-native.ts",
|
|
44
|
+
"lib/runtime-metrics-children.ts", "extensions/runtime-metrics.ts"]) {
|
|
45
|
+
const source = await readFile(join(root, path), "utf8");
|
|
46
|
+
assertNoTrailingWhitespace(source, path);
|
|
47
|
+
stripTypeScriptTypes(source, { mode: "strip" });
|
|
48
|
+
}
|
|
39
49
|
const runtime = join(root, "runtime");
|
|
40
50
|
if (mode === "--write") await mkdir(runtime, { recursive: true });
|
|
41
51
|
const drift = [];
|
|
@@ -57,7 +67,7 @@ async function main() {
|
|
|
57
67
|
if (drift.length > 0) {
|
|
58
68
|
throw new Error(`generated runtime is stale: ${drift.join(", ")}`);
|
|
59
69
|
}
|
|
60
|
-
process.stdout.write(`runtime ${mode === "--write" ? "generated" : "matches TypeScript sources"} (${sources.length} modules)\n`);
|
|
70
|
+
process.stdout.write(`runtime ${mode === "--write" ? "generated" : "matches TypeScript sources"} (${sources.length} generated modules; one-shot metrics sources validated)\n`);
|
|
61
71
|
}
|
|
62
72
|
|
|
63
73
|
main().catch((error) => {
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Type gate for a project that does not compile cleanly yet.
|
|
3
|
+
//
|
|
4
|
+
// `tsc --noEmit` reports a fixed set of diagnostics on this repository today, so
|
|
5
|
+
// the gate is a ratchet rather than a clean pass: it fails when the number of
|
|
6
|
+
// diagnostics grows for any (file, error code) pair, or when the total grows.
|
|
7
|
+
// Fixing diagnostics and refreshing the baseline is the intended way to shrink
|
|
8
|
+
// it.
|
|
9
|
+
//
|
|
10
|
+
// Known blind spot, stated rather than hidden: keying on (file, code) counts is
|
|
11
|
+
// what keeps the baseline stable while files are edited, because line numbers
|
|
12
|
+
// shift. A single change that removes one diagnostic and introduces another of
|
|
13
|
+
// the same code in the same file therefore passes. The total check catches that
|
|
14
|
+
// whenever the counts differ at all.
|
|
15
|
+
//
|
|
16
|
+
// Usage:
|
|
17
|
+
// node scripts/check-types.mjs fail on any new diagnostic
|
|
18
|
+
// node scripts/check-types.mjs --update rewrite the baseline and exit 0
|
|
19
|
+
|
|
20
|
+
import { execFileSync } from "node:child_process";
|
|
21
|
+
import { readFileSync, writeFileSync } from "node:fs";
|
|
22
|
+
import { createRequire } from "node:module";
|
|
23
|
+
import { dirname, join, relative } from "node:path";
|
|
24
|
+
import { fileURLToPath } from "node:url";
|
|
25
|
+
|
|
26
|
+
const packageRoot = dirname(dirname(fileURLToPath(import.meta.url)));
|
|
27
|
+
const baselinePath = join(packageRoot, "scripts", "types-baseline.json");
|
|
28
|
+
const configPath = join(packageRoot, "tsconfig.json");
|
|
29
|
+
const DIAGNOSTIC = /^(.*?)\((\d+),(\d+)\): error (TS\d+): (.*)$/;
|
|
30
|
+
const REPORT_LIMIT = 20;
|
|
31
|
+
|
|
32
|
+
function relativeToPackage(file) {
|
|
33
|
+
return relative(packageRoot, file).split("\\").join("/");
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function compile() {
|
|
37
|
+
let tsc;
|
|
38
|
+
try {
|
|
39
|
+
tsc = createRequire(import.meta.url).resolve("typescript/bin/tsc");
|
|
40
|
+
} catch {
|
|
41
|
+
console.error("types: cannot resolve the TypeScript compiler. Run `pnpm install` first.");
|
|
42
|
+
process.exit(1);
|
|
43
|
+
}
|
|
44
|
+
try {
|
|
45
|
+
execFileSync(process.execPath, [tsc, "-p", configPath], {
|
|
46
|
+
cwd: packageRoot,
|
|
47
|
+
encoding: "utf8",
|
|
48
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
49
|
+
});
|
|
50
|
+
return "";
|
|
51
|
+
} catch (error) {
|
|
52
|
+
// tsc exits non-zero when it reports diagnostics and writes them to stdout.
|
|
53
|
+
return `${error.stdout ?? ""}${error.stderr ?? ""}`;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function summarize(output) {
|
|
58
|
+
const counts = new Map();
|
|
59
|
+
const samples = new Map();
|
|
60
|
+
let total = 0;
|
|
61
|
+
for (const line of output.split(/\r?\n/)) {
|
|
62
|
+
const match = DIAGNOSTIC.exec(line);
|
|
63
|
+
if (!match) continue;
|
|
64
|
+
const key = `${relativeToPackage(match[1])} ${match[4]}`;
|
|
65
|
+
counts.set(key, (counts.get(key) ?? 0) + 1);
|
|
66
|
+
if (!samples.has(key)) samples.set(key, match[5].trim());
|
|
67
|
+
total += 1;
|
|
68
|
+
}
|
|
69
|
+
return { counts, samples, total };
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function writeBaseline(summary) {
|
|
73
|
+
const byFileAndCode = Object.fromEntries([...summary.counts].sort(([left], [right]) => left.localeCompare(right)));
|
|
74
|
+
const payload = {
|
|
75
|
+
note: "Recorded TypeScript diagnostics. Regenerate with `node scripts/check-types.mjs --update` after fixing or deliberately accepting diagnostics. This file may only shrink without a review decision.",
|
|
76
|
+
total: summary.total,
|
|
77
|
+
byFileAndCode,
|
|
78
|
+
};
|
|
79
|
+
writeFileSync(baselinePath, `${JSON.stringify(payload, null, 2)}\n`);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const summary = summarize(compile());
|
|
83
|
+
|
|
84
|
+
if (process.argv.includes("--update")) {
|
|
85
|
+
writeBaseline(summary);
|
|
86
|
+
console.log(`types: baseline recorded ${summary.total} diagnostic(s) across ${summary.counts.size} file/code pair(s)`);
|
|
87
|
+
process.exit(0);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
let baseline;
|
|
91
|
+
try {
|
|
92
|
+
baseline = JSON.parse(readFileSync(baselinePath, "utf8"));
|
|
93
|
+
} catch (error) {
|
|
94
|
+
console.error(`types: cannot read ${relativeToPackage(baselinePath)}: ${error instanceof Error ? error.message : String(error)}`);
|
|
95
|
+
process.exit(1);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const regressions = [];
|
|
99
|
+
for (const [key, count] of summary.counts) {
|
|
100
|
+
const recorded = baseline.byFileAndCode[key] ?? 0;
|
|
101
|
+
if (count > recorded) regressions.push({ key, added: count - recorded, message: summary.samples.get(key) ?? "" });
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const totalGrew = summary.total > baseline.total;
|
|
105
|
+
|
|
106
|
+
if (regressions.length > 0 || totalGrew) {
|
|
107
|
+
if (regressions.length > 0) {
|
|
108
|
+
console.error(`types: ${regressions.length} file/code pair(s) report more diagnostics than recorded:`);
|
|
109
|
+
for (const regression of regressions.slice(0, REPORT_LIMIT)) {
|
|
110
|
+
console.error(` +${regression.added} ${regression.key} ${regression.message.slice(0, 100)}`);
|
|
111
|
+
}
|
|
112
|
+
if (regressions.length > REPORT_LIMIT) console.error(` ... and ${regressions.length - REPORT_LIMIT} more`);
|
|
113
|
+
}
|
|
114
|
+
if (totalGrew) {
|
|
115
|
+
console.error(`types: total ${summary.total} exceeds the recorded ${baseline.total}`);
|
|
116
|
+
}
|
|
117
|
+
console.error("Fix the new diagnostics, or run `node scripts/check-types.mjs --update` when they are deliberately accepted.");
|
|
118
|
+
process.exit(1);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const improved = [...Object.entries(baseline.byFileAndCode)].filter(([key, count]) => (summary.counts.get(key) ?? 0) < count).length;
|
|
122
|
+
console.log(
|
|
123
|
+
`types: ${summary.total} recorded diagnostic(s), no regressions` +
|
|
124
|
+
(improved > 0 ? `; ${improved} file/code pair(s) improved, run --update to shrink the baseline` : ""),
|
|
125
|
+
);
|