@brainervirus/workit-core 1.2.1 → 1.3.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.
- package/package.json +1 -1
- package/skills/workit-babysit/SKILL.md +20 -26
- package/skills/workit-behavioral-tdd/SKILL.md +0 -5
- package/skills/workit-blast-radius/SKILL.md +0 -5
- package/skills/workit-challenge/SKILL.md +30 -47
- package/skills/workit-debug/SKILL.md +5 -27
- package/skills/workit-deslop/SKILL.md +0 -5
- package/skills/workit-diagram/SKILL.md +0 -5
- package/skills/workit-green-run/SKILL.md +0 -5
- package/skills/workit-handoff/SKILL.md +2 -8
- package/skills/workit-implement/SKILL.md +14 -20
- package/skills/workit-mockup/SKILL.md +0 -5
- package/skills/workit-plan/SKILL.md +30 -63
- package/skills/workit-review/SKILL.md +0 -5
- package/skills/workit-steer/SKILL.md +27 -27
- package/src/core/authority.ts +6 -1
- package/src/core/auto-approval.ts +34 -14
- package/src/core/branch.ts +293 -42
- package/src/core/config-conversion.ts +4 -4
- package/src/core/config-guard.ts +1 -7
- package/src/core/config.ts +1 -1
- package/src/core/cutover.ts +1207 -140
- package/src/core/doctor.ts +155 -82
- package/src/core/external-action-effects.ts +886 -172
- package/src/core/external-action.ts +222 -64
- package/src/core/init.ts +25 -115
- package/src/core/legacy-ownership.ts +47 -0
- package/src/core/methods.ts +65 -52
- package/src/core/policy-resolver.ts +13 -5
- package/src/core/ports/pr-create.ts +23 -15
- package/src/core/pr-create.ts +374 -84
- package/src/core/registration.ts +0 -15
- package/src/core/repo-tools.ts +0 -1
- package/src/core/route-intent.ts +34 -82
- package/src/core/safe-write.ts +66 -1
- package/src/core/setup-state.ts +7 -0
- package/src/core/setup.ts +175 -82
- package/src/core/task-contract.ts +66 -6
- package/src/core/task-engine.ts +47 -2
- package/src/core/task-store.ts +159 -1
- package/src/core/tracker-issues.ts +55 -118
- package/src/core/vcs-config.ts +178 -187
- package/src/core/workspaces.ts +554 -46
- package/src/core.ts +3 -2
- package/src/core/ports/vcs-token-create-urls.ts +0 -4
package/src/core/doctor.ts
CHANGED
|
@@ -2,12 +2,9 @@
|
|
|
2
2
|
// installed Workit surfaces — pins, versions, assets, launchers, runtimes,
|
|
3
3
|
// utilities, registrations, config, workspace match, credential metadata, and
|
|
4
4
|
// log writability — with no network access except the optional registry probe
|
|
5
|
-
// behind the stale-install comparison (CA-04) and
|
|
6
|
-
// probes
|
|
7
|
-
//
|
|
8
|
-
// the identity check reads a token file solely to build an Authorization
|
|
9
|
-
// header for a login lookup — token bytes never enter the report, any fix
|
|
10
|
-
// text, or any log event.
|
|
5
|
+
// behind the stale-install comparison (CA-04) and fail-open provider CLI
|
|
6
|
+
// identity probes. Credentials are handled by gh/glab; token values never enter
|
|
7
|
+
// the report, any fix text, or any log event.
|
|
11
8
|
import { spawnSync } from "node:child_process";
|
|
12
9
|
import {
|
|
13
10
|
existsSync,
|
|
@@ -34,7 +31,7 @@ import {
|
|
|
34
31
|
cursorMcpServerEntry,
|
|
35
32
|
isWorkitPlugin,
|
|
36
33
|
} from "./registration";
|
|
37
|
-
import { resolveWorkspaceFrom } from "./workspaces";
|
|
34
|
+
import { readWorkspacesResult, resolveWorkspaceFrom } from "./workspaces";
|
|
38
35
|
import { validateCursorSkills, WORKIT_METHOD_SKILLS } from "./skill-manifests";
|
|
39
36
|
import {
|
|
40
37
|
classifyHostGeneration,
|
|
@@ -66,6 +63,7 @@ export type DoctorCheckId =
|
|
|
66
63
|
| "workspace_mismatch"
|
|
67
64
|
| "credential_metadata"
|
|
68
65
|
| "github_identity"
|
|
66
|
+
| "gitlab_identity"
|
|
69
67
|
| "log_writable"
|
|
70
68
|
| "legacy_component"
|
|
71
69
|
| "mixed_generation"
|
|
@@ -1166,22 +1164,36 @@ const checkMalformedConfig = (res: Resolved): DoctorCheck => {
|
|
|
1166
1164
|
};
|
|
1167
1165
|
|
|
1168
1166
|
const checkWorkspaceMismatch = (res: Resolved): DoctorCheck => {
|
|
1169
|
-
const
|
|
1170
|
-
|
|
1167
|
+
const result = readWorkspacesResult(res.configDir);
|
|
1168
|
+
const file = result.path;
|
|
1169
|
+
if (result.status === "missing" || (result.status === "valid" && result.entries.length === 0))
|
|
1171
1170
|
return {
|
|
1172
1171
|
id: "workspace_mismatch",
|
|
1173
1172
|
status: "pass",
|
|
1174
1173
|
detail: "no workspaces configured",
|
|
1175
1174
|
};
|
|
1176
|
-
|
|
1177
|
-
if (!Array.isArray(ws?.workspaces)) {
|
|
1175
|
+
if (result.status === "malformed" || result.status === "invalid")
|
|
1178
1176
|
return {
|
|
1179
1177
|
id: "workspace_mismatch",
|
|
1180
|
-
status: "
|
|
1181
|
-
detail:
|
|
1178
|
+
status: "fail",
|
|
1179
|
+
detail: result.error ?? `${file} has invalid workspace configuration`,
|
|
1180
|
+
fix: `Repair workspace configuration in ${file}`,
|
|
1181
|
+
};
|
|
1182
|
+
let match;
|
|
1183
|
+
try {
|
|
1184
|
+
match = resolveWorkspaceFrom(
|
|
1185
|
+
res.cwd,
|
|
1186
|
+
res.configDir,
|
|
1187
|
+
res.env.WORKFLOW_WORKSPACE_NAME?.trim() || undefined,
|
|
1188
|
+
);
|
|
1189
|
+
} catch (error) {
|
|
1190
|
+
return {
|
|
1191
|
+
id: "workspace_mismatch",
|
|
1192
|
+
status: "fail",
|
|
1193
|
+
detail: error instanceof Error ? error.message : String(error),
|
|
1194
|
+
fix: `Choose a matching workspace with WORKFLOW_WORKSPACE_NAME or repair ${file}`,
|
|
1182
1195
|
};
|
|
1183
1196
|
}
|
|
1184
|
-
const match = resolveWorkspaceFrom(res.cwd, res.configDir);
|
|
1185
1197
|
if (match)
|
|
1186
1198
|
return {
|
|
1187
1199
|
id: "workspace_mismatch",
|
|
@@ -1218,26 +1230,6 @@ const checkCredentialMetadata = (res: Resolved): DoctorCheck => {
|
|
|
1218
1230
|
tokenPaths.push(path.join(res.configDir, "youtrack.token"));
|
|
1219
1231
|
}
|
|
1220
1232
|
|
|
1221
|
-
const vcsJson = readJson(path.join(res.configDir, "vcs.json"));
|
|
1222
|
-
// Only the active VCS provider's token is required. vcs.json scaffolds both
|
|
1223
|
-
// github and gitlab blocks for switching later — inactive tokenFile paths
|
|
1224
|
-
// must not fail the doctor when provider is set to the other host.
|
|
1225
|
-
const active =
|
|
1226
|
-
typeof vcsJson?.provider === "string" ? vcsJson.provider.trim().toLowerCase() : null;
|
|
1227
|
-
const vcsKeys =
|
|
1228
|
-
active === "gitlab" || active === "github"
|
|
1229
|
-
? ([active] as const)
|
|
1230
|
-
: (["gitlab", "github"] as const);
|
|
1231
|
-
for (const key of vcsKeys) {
|
|
1232
|
-
const provider = vcsJson?.[key];
|
|
1233
|
-
if (provider && typeof provider === "object") {
|
|
1234
|
-
const tf = provider.tokenFile;
|
|
1235
|
-
tokenPaths.push(typeof tf === "string" ? tf : path.join(res.configDir, `${key}.token`));
|
|
1236
|
-
} else if (!vcsJson && existsSync(path.join(res.configDir, `${key}.token`))) {
|
|
1237
|
-
tokenPaths.push(path.join(res.configDir, `${key}.token`));
|
|
1238
|
-
}
|
|
1239
|
-
}
|
|
1240
|
-
|
|
1241
1233
|
if (tokenPaths.length === 0) {
|
|
1242
1234
|
return {
|
|
1243
1235
|
id: "credential_metadata",
|
|
@@ -1273,15 +1265,13 @@ const checkCredentialMetadata = (res: Resolved): DoctorCheck => {
|
|
|
1273
1265
|
};
|
|
1274
1266
|
};
|
|
1275
1267
|
|
|
1276
|
-
//
|
|
1277
|
-
// usernames (safe to report); the token itself only ever travels as an
|
|
1278
|
-
// Authorization header value and never enters details, fixes, or logs.
|
|
1268
|
+
// Provider CLI and SSH identities are public usernames; Workit never reads a VCS token file.
|
|
1279
1269
|
export type IdentitySurface = { surface: string; login: string | null };
|
|
1280
1270
|
|
|
1281
1271
|
export type IdentityProbes = {
|
|
1282
1272
|
origin: (cwd: string) => string | null;
|
|
1283
1273
|
ghLogin: (env: NodeJS.ProcessEnv) => string | null;
|
|
1284
|
-
|
|
1274
|
+
glabLogin?: (env: NodeJS.ProcessEnv) => string | null;
|
|
1285
1275
|
sshLogin: (host: string) => string | null;
|
|
1286
1276
|
};
|
|
1287
1277
|
|
|
@@ -1298,31 +1288,82 @@ export const githubHostFromRemote = (remote: string): string | null => {
|
|
|
1298
1288
|
}
|
|
1299
1289
|
};
|
|
1300
1290
|
|
|
1301
|
-
|
|
1291
|
+
const identityProvider = (
|
|
1292
|
+
host: string,
|
|
1293
|
+
config: Record<string, any> | null,
|
|
1294
|
+
workspaceProvider?: unknown,
|
|
1295
|
+
): "github" | "gitlab" | null =>
|
|
1296
|
+
workspaceProvider === "github" || workspaceProvider === "gitlab"
|
|
1297
|
+
? workspaceProvider
|
|
1298
|
+
: host === "github.com"
|
|
1299
|
+
? "github"
|
|
1300
|
+
: host === "gitlab.com"
|
|
1301
|
+
? "gitlab"
|
|
1302
|
+
: config?.provider === "github" || config?.provider === "gitlab"
|
|
1303
|
+
? config.provider
|
|
1304
|
+
: config?.github
|
|
1305
|
+
? "github"
|
|
1306
|
+
: config?.gitlab
|
|
1307
|
+
? "gitlab"
|
|
1308
|
+
: null;
|
|
1309
|
+
|
|
1310
|
+
const providerIdentityFinding = (
|
|
1311
|
+
provider: "github" | "gitlab",
|
|
1312
|
+
surfaces: IdentitySurface[],
|
|
1313
|
+
): DoctorCheck => {
|
|
1314
|
+
const id = `${provider}_identity` as "github_identity" | "gitlab_identity";
|
|
1315
|
+
const label = provider === "github" ? "GitHub" : "GitLab";
|
|
1302
1316
|
const resolved = surfaces.filter(
|
|
1303
1317
|
(entry): entry is { surface: string; login: string } => typeof entry.login === "string",
|
|
1304
1318
|
);
|
|
1305
1319
|
const distinct = [...new Set(resolved.map((entry) => entry.login.toLowerCase()))];
|
|
1306
1320
|
if (distinct.length < 2) {
|
|
1307
1321
|
return {
|
|
1308
|
-
id
|
|
1322
|
+
id,
|
|
1309
1323
|
status: "pass",
|
|
1310
1324
|
detail:
|
|
1311
1325
|
resolved.length === 0
|
|
1312
|
-
?
|
|
1313
|
-
:
|
|
1326
|
+
? `no ${label} identities configured`
|
|
1327
|
+
: `single ${label} identity across configured surfaces`,
|
|
1314
1328
|
};
|
|
1315
1329
|
}
|
|
1316
1330
|
return {
|
|
1317
|
-
id
|
|
1331
|
+
id,
|
|
1318
1332
|
status: "warn",
|
|
1319
|
-
detail:
|
|
1333
|
+
detail: `${label} identities disagree across configured surfaces: ${resolved
|
|
1320
1334
|
.map((entry) => `${entry.surface} reports "${entry.login}"`)
|
|
1321
1335
|
.join("; ")}. Operations may land under the wrong account.`,
|
|
1322
|
-
fix:
|
|
1336
|
+
fix:
|
|
1337
|
+
provider === "github"
|
|
1338
|
+
? "Align the effective gh account and SSH identity via gh auth switch/login and your Git configuration"
|
|
1339
|
+
: "Align the effective glab account and SSH identity via glab auth login and your Git configuration",
|
|
1323
1340
|
};
|
|
1324
1341
|
};
|
|
1325
1342
|
|
|
1343
|
+
export const githubIdentityFinding = (surfaces: IdentitySurface[]): DoctorCheck =>
|
|
1344
|
+
providerIdentityFinding("github", surfaces);
|
|
1345
|
+
|
|
1346
|
+
export const gitlabIdentityFinding = (surfaces: IdentitySurface[]): DoctorCheck =>
|
|
1347
|
+
providerIdentityFinding("gitlab", surfaces);
|
|
1348
|
+
|
|
1349
|
+
const workspaceProviderForIdentity = (
|
|
1350
|
+
cwd: string,
|
|
1351
|
+
configDir: string,
|
|
1352
|
+
env: NodeJS.ProcessEnv,
|
|
1353
|
+
): { provider?: unknown; error?: string } => {
|
|
1354
|
+
try {
|
|
1355
|
+
return {
|
|
1356
|
+
provider: resolveWorkspaceFrom(
|
|
1357
|
+
cwd,
|
|
1358
|
+
configDir,
|
|
1359
|
+
env.WORKFLOW_WORKSPACE_NAME?.trim() || undefined,
|
|
1360
|
+
)?.vcs?.provider,
|
|
1361
|
+
};
|
|
1362
|
+
} catch (error) {
|
|
1363
|
+
return { error: error instanceof Error ? error.message : String(error) };
|
|
1364
|
+
}
|
|
1365
|
+
};
|
|
1366
|
+
|
|
1326
1367
|
const defaultIdentityProbes = (env: NodeJS.ProcessEnv): IdentityProbes => ({
|
|
1327
1368
|
origin: (cwd) => {
|
|
1328
1369
|
try {
|
|
@@ -1345,18 +1386,11 @@ const defaultIdentityProbes = (env: NodeJS.ProcessEnv): IdentityProbes => ({
|
|
|
1345
1386
|
return null;
|
|
1346
1387
|
}
|
|
1347
1388
|
},
|
|
1348
|
-
|
|
1349
|
-
let token = "";
|
|
1350
|
-
try {
|
|
1351
|
-
token = readFileSync(tokenFile, "utf8").trim();
|
|
1352
|
-
} catch {
|
|
1353
|
-
return null;
|
|
1354
|
-
}
|
|
1355
|
-
if (!token || token === TOKEN_PLACEHOLDER || token.startsWith(TOKEN_PLACEHOLDER)) return null;
|
|
1389
|
+
glabLogin: (e) => {
|
|
1356
1390
|
try {
|
|
1357
|
-
const out = spawnSync("
|
|
1391
|
+
const out = spawnSync("glab", ["api", "user", "--jq", ".username"], {
|
|
1358
1392
|
encoding: "utf8",
|
|
1359
|
-
env:
|
|
1393
|
+
env: e,
|
|
1360
1394
|
});
|
|
1361
1395
|
const login = (out.stdout ?? "").trim();
|
|
1362
1396
|
return out.status === 0 && login ? login : null;
|
|
@@ -1375,7 +1409,7 @@ const defaultIdentityProbes = (env: NodeJS.ProcessEnv): IdentityProbes => ({
|
|
|
1375
1409
|
},
|
|
1376
1410
|
);
|
|
1377
1411
|
const text = `${out.stdout ?? ""}${out.stderr ?? ""}`;
|
|
1378
|
-
const match = /^Hi ([^!]+)
|
|
1412
|
+
const match = /^(?:Hi |Welcome to GitLab, @)([^!]+)!/m.exec(text);
|
|
1379
1413
|
return match ? match[1].trim() || null : null;
|
|
1380
1414
|
} catch {
|
|
1381
1415
|
return null;
|
|
@@ -1383,40 +1417,73 @@ const defaultIdentityProbes = (env: NodeJS.ProcessEnv): IdentityProbes => ({
|
|
|
1383
1417
|
},
|
|
1384
1418
|
});
|
|
1385
1419
|
|
|
1386
|
-
const githubTokenFile = (res: Pick<Resolved, "configDir">): string | null => {
|
|
1387
|
-
const vcsJson = readJson(path.join(res.configDir, "vcs.json"));
|
|
1388
|
-
const provider = vcsJson?.github;
|
|
1389
|
-
if (provider && typeof provider === "object") {
|
|
1390
|
-
const tokenFile = (provider as Record<string, unknown>).tokenFile;
|
|
1391
|
-
const raw =
|
|
1392
|
-
typeof tokenFile === "string" ? tokenFile : path.join(res.configDir, "github.token");
|
|
1393
|
-
return path.isAbsolute(raw) ? raw : path.resolve(res.configDir, raw);
|
|
1394
|
-
}
|
|
1395
|
-
if (!vcsJson) {
|
|
1396
|
-
const fallback = path.join(res.configDir, "github.token");
|
|
1397
|
-
return existsSync(fallback) ? fallback : null;
|
|
1398
|
-
}
|
|
1399
|
-
return null;
|
|
1400
|
-
};
|
|
1401
|
-
|
|
1402
1420
|
export const checkGithubIdentity = (
|
|
1403
1421
|
res: Pick<Resolved, "cwd" | "configDir" | "env">,
|
|
1404
1422
|
probes: IdentityProbes = defaultIdentityProbes(res.env),
|
|
1405
1423
|
): DoctorCheck => {
|
|
1406
|
-
const
|
|
1407
|
-
if (!
|
|
1424
|
+
const remoteHost = githubHostFromRemote(probes.origin(res.cwd) ?? "");
|
|
1425
|
+
if (!remoteHost)
|
|
1408
1426
|
return { id: "github_identity", status: "pass", detail: "no GitHub remote configured" };
|
|
1409
|
-
const
|
|
1410
|
-
const
|
|
1411
|
-
if (
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1427
|
+
const vcsJson = readJson(res.env.WORKFLOW_VCS_CONFIG ?? path.join(res.configDir, "vcs.json"));
|
|
1428
|
+
const workspace = workspaceProviderForIdentity(res.cwd, res.configDir, res.env);
|
|
1429
|
+
if (workspace.error)
|
|
1430
|
+
return {
|
|
1431
|
+
id: "github_identity",
|
|
1432
|
+
status: "fail",
|
|
1433
|
+
detail: workspace.error,
|
|
1434
|
+
fix: "Repair workspace configuration or choose a matching workspace",
|
|
1435
|
+
};
|
|
1436
|
+
const provider = identityProvider(remoteHost, vcsJson, workspace.provider);
|
|
1437
|
+
if (provider !== "github")
|
|
1438
|
+
return { id: "github_identity", status: "pass", detail: "no GitHub remote configured" };
|
|
1439
|
+
const cliHost = String(vcsJson?.github?.host ?? "github.com").toLowerCase();
|
|
1440
|
+
const cliLogin = probes.ghLogin({ ...res.env, GH_HOST: cliHost });
|
|
1441
|
+
if (!cliLogin)
|
|
1442
|
+
return {
|
|
1443
|
+
id: "github_identity",
|
|
1444
|
+
status: "fail",
|
|
1445
|
+
detail: "gh CLI is not authenticated",
|
|
1446
|
+
fix: "Run gh auth login",
|
|
1447
|
+
};
|
|
1448
|
+
const surfaces: IdentitySurface[] = [{ surface: "gh CLI", login: cliLogin }];
|
|
1449
|
+
surfaces.push({ surface: "SSH", login: probes.sshLogin(remoteHost) });
|
|
1417
1450
|
return githubIdentityFinding(surfaces);
|
|
1418
1451
|
};
|
|
1419
1452
|
|
|
1453
|
+
export const checkGitLabIdentity = (
|
|
1454
|
+
res: Pick<Resolved, "cwd" | "configDir" | "env">,
|
|
1455
|
+
probes: IdentityProbes = defaultIdentityProbes(res.env),
|
|
1456
|
+
): DoctorCheck => {
|
|
1457
|
+
const remoteHost = githubHostFromRemote(probes.origin(res.cwd) ?? "");
|
|
1458
|
+
if (!remoteHost)
|
|
1459
|
+
return { id: "gitlab_identity", status: "pass", detail: "no GitLab remote configured" };
|
|
1460
|
+
const vcsJson = readJson(res.env.WORKFLOW_VCS_CONFIG ?? path.join(res.configDir, "vcs.json"));
|
|
1461
|
+
const workspace = workspaceProviderForIdentity(res.cwd, res.configDir, res.env);
|
|
1462
|
+
if (workspace.error)
|
|
1463
|
+
return {
|
|
1464
|
+
id: "gitlab_identity",
|
|
1465
|
+
status: "fail",
|
|
1466
|
+
detail: workspace.error,
|
|
1467
|
+
fix: "Repair workspace configuration or choose a matching workspace",
|
|
1468
|
+
};
|
|
1469
|
+
const provider = identityProvider(remoteHost, vcsJson, workspace.provider);
|
|
1470
|
+
if (provider !== "gitlab")
|
|
1471
|
+
return { id: "gitlab_identity", status: "pass", detail: "no GitLab remote configured" };
|
|
1472
|
+
const cliHost = String(vcsJson?.gitlab?.host ?? "gitlab.com").toLowerCase();
|
|
1473
|
+
const cliLogin = probes.glabLogin?.({ ...res.env, GITLAB_HOST: cliHost }) ?? null;
|
|
1474
|
+
if (!cliLogin)
|
|
1475
|
+
return {
|
|
1476
|
+
id: "gitlab_identity",
|
|
1477
|
+
status: "fail",
|
|
1478
|
+
detail: "glab CLI is not authenticated",
|
|
1479
|
+
fix: "Run glab auth login",
|
|
1480
|
+
};
|
|
1481
|
+
return gitlabIdentityFinding([
|
|
1482
|
+
{ surface: "glab CLI", login: cliLogin },
|
|
1483
|
+
{ surface: "SSH", login: probes.sshLogin(remoteHost) },
|
|
1484
|
+
]);
|
|
1485
|
+
};
|
|
1486
|
+
|
|
1420
1487
|
const checkLogWritable = (res: Resolved): DoctorCheck => {
|
|
1421
1488
|
const logsDir = path.join(res.stateDir, "logs");
|
|
1422
1489
|
// Fixed-name probe: even a killed process leaves at most one bounded file that
|
|
@@ -1458,6 +1525,11 @@ const generationPaths = (res: Resolved) => ({
|
|
|
1458
1525
|
cursorSettings: res.cursorSettings,
|
|
1459
1526
|
cursorMcp: res.cursorMcp,
|
|
1460
1527
|
cursorPluginDir: res.cursorPluginDir,
|
|
1528
|
+
piConfig: path.join(res.home, ".pi", "config.json"),
|
|
1529
|
+
piSettings: path.join(
|
|
1530
|
+
res.env.PI_CODING_AGENT_DIR ?? path.join(res.home, ".pi", "agent"),
|
|
1531
|
+
"settings.json",
|
|
1532
|
+
),
|
|
1461
1533
|
sessions: res.sessions,
|
|
1462
1534
|
});
|
|
1463
1535
|
|
|
@@ -1627,6 +1699,7 @@ const RUN_CHECKS: Array<(res: Resolved) => DoctorCheck> = [
|
|
|
1627
1699
|
checkWorkspaceMismatch,
|
|
1628
1700
|
checkCredentialMetadata,
|
|
1629
1701
|
checkGithubIdentity,
|
|
1702
|
+
checkGitLabIdentity,
|
|
1630
1703
|
checkLogWritable,
|
|
1631
1704
|
checkMixedGeneration,
|
|
1632
1705
|
checkLegacyComponent,
|