@codyswann/lisa 3.40.4 → 3.41.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/all/copy-overwrite/scripts/lisa-gates.mjs +234 -1
- package/dist/cli/doctor-lisa-owned-artifacts.d.ts +4 -2
- package/dist/cli/doctor-lisa-owned-artifacts.d.ts.map +1 -1
- package/dist/cli/doctor-lisa-owned-artifacts.js +46 -32
- package/dist/cli/doctor-lisa-owned-artifacts.js.map +1 -1
- package/dist/cli/doctor-lisa-owned-universal.d.ts +26 -0
- package/dist/cli/doctor-lisa-owned-universal.d.ts.map +1 -0
- package/dist/cli/doctor-lisa-owned-universal.js +50 -0
- package/dist/cli/doctor-lisa-owned-universal.js.map +1 -0
- package/dist/cli/doctor-skip-jobs-migration.d.ts +48 -0
- package/dist/cli/doctor-skip-jobs-migration.d.ts.map +1 -0
- package/dist/cli/doctor-skip-jobs-migration.js +271 -0
- package/dist/cli/doctor-skip-jobs-migration.js.map +1 -0
- package/dist/cli/doctor.d.ts.map +1 -1
- package/dist/cli/doctor.js +7 -0
- package/dist/cli/doctor.js.map +1 -1
- package/dist/core/lisa-owned-hash-ledger.d.ts.map +1 -1
- package/dist/core/lisa-owned-hash-ledger.js +1 -0
- package/dist/core/lisa-owned-hash-ledger.js.map +1 -1
- package/dist/core/upstream-evidence-manifest.d.ts.map +1 -1
- package/dist/core/upstream-evidence-manifest.js +8 -2
- package/dist/core/upstream-evidence-manifest.js.map +1 -1
- package/package.json +1 -1
- package/plugins/lisa/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa/.codex-plugin/skills/lisa-doctor/SKILL.md +20 -0
- package/plugins/lisa/skills/lisa-doctor/SKILL.md +20 -0
- package/plugins/lisa-agy/plugin.json +1 -1
- package/plugins/lisa-agy/skills/lisa-doctor/SKILL.md +20 -0
- package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk-agy/plugin.json +1 -1
- package/plugins/lisa-cdk-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-copilot/skills/lisa-doctor/SKILL.md +20 -0
- package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cursor/skills/lisa-doctor/SKILL.md +20 -0
- package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-expo/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-expo-agy/plugin.json +1 -1
- package/plugins/lisa-expo-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-expo-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-agy/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs-agy/plugin.json +1 -1
- package/plugins/lisa-nestjs-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw-agy/plugin.json +1 -1
- package/plugins/lisa-openclaw-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser-agy/plugin.json +1 -1
- package/plugins/lisa-phaser-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-rails-agy/plugin.json +1 -1
- package/plugins/lisa-rails-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript-agy/plugin.json +1 -1
- package/plugins/lisa-typescript-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki-agy/plugin.json +1 -1
- package/plugins/lisa-wiki-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/src/base/skills/lisa-doctor/SKILL.md +20 -0
|
@@ -452,6 +452,213 @@ export const REGISTRY = Object.freeze({
|
|
|
452
452
|
},
|
|
453
453
|
});
|
|
454
454
|
|
|
455
|
+
/**
|
|
456
|
+
* Every `quality.yml` job that resolves through the gate façade, and its gate.
|
|
457
|
+
*
|
|
458
|
+
* This table and the two below exist so that the answer to "which gate replaces
|
|
459
|
+
* this `skip_jobs` token" can be read in a CONSUMER. It could not be, before:
|
|
460
|
+
* the pairs lived in one of Lisa's own test fixtures, which no consumer
|
|
461
|
+
* installs, so `lisa doctor` running in a caller repository and an agent working
|
|
462
|
+
* in a caller checkout both had nothing authoritative to consult.
|
|
463
|
+
*
|
|
464
|
+
* Eleven of the pairs cannot be recovered by transforming the name — `lint` is
|
|
465
|
+
* `code-style`, `sg_scan` is `structural-rules`, `work_item_traceability` is
|
|
466
|
+
* `traceability`. An underscore-to-hyphen guess gets a minority of them right,
|
|
467
|
+
* and being wrong here does not break a build: it declares the WRONG gate
|
|
468
|
+
* `off`, so a check silently stops running while the configuration reads
|
|
469
|
+
* deliberate. That is why this ships as data rather than as advice.
|
|
470
|
+
*
|
|
471
|
+
* It is STATIC on purpose. A consumer holds no copy of `quality.yml` — it calls
|
|
472
|
+
* the reusable workflow by ref — so the table cannot be derived where it is
|
|
473
|
+
* read. `tests/integration/quality-gate-skip-jobs-mapping.test.ts` derives it
|
|
474
|
+
* from the workflow in the one repository that has the workflow and fails when
|
|
475
|
+
* the two disagree, which is what keeps a static copy true.
|
|
476
|
+
*/
|
|
477
|
+
export const QUALITY_JOB_GATES = Object.freeze({
|
|
478
|
+
lint: "code-style",
|
|
479
|
+
lint_slow: "code-style-slow",
|
|
480
|
+
typecheck: "type-correctness",
|
|
481
|
+
verification_coverage: "coverage-adequacy",
|
|
482
|
+
test_unit: "test-correctness",
|
|
483
|
+
test_mutation: "test-meaningfulness",
|
|
484
|
+
test_integration: "test-integration",
|
|
485
|
+
playwright_e2e_aggregate: "e2e-browser",
|
|
486
|
+
format: "format-conformance",
|
|
487
|
+
build: "build-integrity",
|
|
488
|
+
work_item_traceability: "traceability",
|
|
489
|
+
performance_budget: "performance-budget",
|
|
490
|
+
test_node_suites: "test-node-suites",
|
|
491
|
+
environment_reset: "environment-reset",
|
|
492
|
+
environment_reseed: "environment-reseed",
|
|
493
|
+
dead_code: "dead-code",
|
|
494
|
+
sg_scan: "structural-rules",
|
|
495
|
+
npm_security_scan: "dependency-vulnerability",
|
|
496
|
+
});
|
|
497
|
+
|
|
498
|
+
/**
|
|
499
|
+
* Every `skip_jobs` token `quality.yml` honours or advertises, and the jobs it
|
|
500
|
+
* suppresses.
|
|
501
|
+
*
|
|
502
|
+
* Keyed by TOKEN, not by job id, because the two differ and the difference is
|
|
503
|
+
* invisible from either side: the token is `test:unit` and the job is
|
|
504
|
+
* `test_unit`, so a table keyed on job ids answers "no such token" for three of
|
|
505
|
+
* the tokens that do have gates.
|
|
506
|
+
*
|
|
507
|
+
* An empty array is a real entry, not a placeholder. `github_issue` is
|
|
508
|
+
* advertised by the input's own description and honoured by no job at all — it
|
|
509
|
+
* suppresses nothing today, which a consumer deleting it deserves to be told
|
|
510
|
+
* rather than left to infer from a check that never changed.
|
|
511
|
+
*/
|
|
512
|
+
export const SKIP_JOB_TOKENS = Object.freeze({
|
|
513
|
+
lint: Object.freeze(["lint"]),
|
|
514
|
+
lint_slow: Object.freeze(["lint_slow"]),
|
|
515
|
+
typecheck: Object.freeze(["typecheck"]),
|
|
516
|
+
e2e_coverage: Object.freeze(["e2e_coverage"]),
|
|
517
|
+
state_classification: Object.freeze(["state_classification"]),
|
|
518
|
+
bdd_coverage: Object.freeze(["bdd_coverage"]),
|
|
519
|
+
learnings_budget: Object.freeze(["learnings_budget"]),
|
|
520
|
+
"test:unit": Object.freeze(["test_unit"]),
|
|
521
|
+
"test:mutation": Object.freeze(["test_mutation"]),
|
|
522
|
+
"test:integration": Object.freeze(["test_integration"]),
|
|
523
|
+
"test:e2e": Object.freeze(["test_e2e"]),
|
|
524
|
+
maestro_e2e: Object.freeze(["maestro_e2e"]),
|
|
525
|
+
// Three jobs, one token, and only the last of them has a façade. Reporting
|
|
526
|
+
// this as a clean swap would be the most expensive wrong answer in the table:
|
|
527
|
+
// the shards keep running, so an operator who deleted the token would see the
|
|
528
|
+
// work continue and conclude the declaration had not taken effect.
|
|
529
|
+
playwright_e2e: Object.freeze([
|
|
530
|
+
"playwright_e2e_setup",
|
|
531
|
+
"playwright_e2e",
|
|
532
|
+
"playwright_e2e_aggregate",
|
|
533
|
+
]),
|
|
534
|
+
format: Object.freeze(["format"]),
|
|
535
|
+
build: Object.freeze(["build"]),
|
|
536
|
+
skipped_required_checks: Object.freeze(["skipped_required_checks"]),
|
|
537
|
+
threshold_ratchet: Object.freeze(["threshold_ratchet"]),
|
|
538
|
+
work_item_traceability: Object.freeze(["work_item_traceability"]),
|
|
539
|
+
test_node_suites: Object.freeze(["test_node_suites"]),
|
|
540
|
+
environment_reset: Object.freeze(["environment_reset"]),
|
|
541
|
+
environment_reseed: Object.freeze(["environment_reseed"]),
|
|
542
|
+
dead_code: Object.freeze(["dead_code"]),
|
|
543
|
+
sg_scan: Object.freeze(["sg_scan"]),
|
|
544
|
+
floor_collisions: Object.freeze(["floor_collisions"]),
|
|
545
|
+
npm_security_scan: Object.freeze(["npm_security_scan"]),
|
|
546
|
+
sonarcloud: Object.freeze(["sonarcloud"]),
|
|
547
|
+
snyk: Object.freeze(["snyk"]),
|
|
548
|
+
secret_scanning: Object.freeze(["secret_scanning"]),
|
|
549
|
+
license_compliance: Object.freeze(["license_compliance"]),
|
|
550
|
+
zap_baseline: Object.freeze(["zap_baseline"]),
|
|
551
|
+
github_issue: Object.freeze([]),
|
|
552
|
+
});
|
|
553
|
+
|
|
554
|
+
/**
|
|
555
|
+
* What a `skip_jobs` token can be resolved to.
|
|
556
|
+
*
|
|
557
|
+
* Four of the five are refusals, which is the point. The failure this table
|
|
558
|
+
* exists to prevent is a confident wrong answer, so anything short of "one gate
|
|
559
|
+
* covers every job this token suppresses" has to be a distinct, nameable
|
|
560
|
+
* outcome rather than a best effort.
|
|
561
|
+
*/
|
|
562
|
+
export const SKIP_JOB_STATUS = [
|
|
563
|
+
"replaceable",
|
|
564
|
+
"partial",
|
|
565
|
+
"unmappable",
|
|
566
|
+
"inert",
|
|
567
|
+
"unknown",
|
|
568
|
+
"moment-illegal",
|
|
569
|
+
];
|
|
570
|
+
|
|
571
|
+
/**
|
|
572
|
+
* Resolve one `skip_jobs` token to the gate declaration that replaces it.
|
|
573
|
+
* @param {string} token A `skip_jobs` token, exactly as the caller spells it.
|
|
574
|
+
* @returns {{token: string, status: string, jobs: string[], gates: string[], gate: string|null, ungated: string[]}} What is known about the token.
|
|
575
|
+
*/
|
|
576
|
+
export function gateForSkipJob(token) {
|
|
577
|
+
const jobs = Object.hasOwn(SKIP_JOB_TOKENS, token)
|
|
578
|
+
? [...SKIP_JOB_TOKENS[token]]
|
|
579
|
+
: null;
|
|
580
|
+
if (jobs === null) {
|
|
581
|
+
// Not a guess declined — a token this workflow has never had. A caller can
|
|
582
|
+
// reach here by typo, by whitespace (`'lint, lint_slow'` yields the token
|
|
583
|
+
// `" lint_slow"`), or by carrying a token from a workflow that predates
|
|
584
|
+
// this one. All three suppress nothing today, and none of them may be
|
|
585
|
+
// answered with the nearest-looking gate.
|
|
586
|
+
return {
|
|
587
|
+
token,
|
|
588
|
+
status: "unknown",
|
|
589
|
+
jobs: [],
|
|
590
|
+
gates: [],
|
|
591
|
+
gate: null,
|
|
592
|
+
ungated: [],
|
|
593
|
+
};
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
const ungated = jobs.filter(job => !Object.hasOwn(QUALITY_JOB_GATES, job));
|
|
597
|
+
const gates = [
|
|
598
|
+
...new Set(
|
|
599
|
+
jobs.flatMap(job =>
|
|
600
|
+
Object.hasOwn(QUALITY_JOB_GATES, job) ? [QUALITY_JOB_GATES[job]] : []
|
|
601
|
+
)
|
|
602
|
+
),
|
|
603
|
+
];
|
|
604
|
+
|
|
605
|
+
const status =
|
|
606
|
+
jobs.length === 0
|
|
607
|
+
? "inert"
|
|
608
|
+
: gates.length === 0
|
|
609
|
+
? "unmappable"
|
|
610
|
+
: gates.length === 1 && ungated.length === 0
|
|
611
|
+
? "replaceable"
|
|
612
|
+
: "partial";
|
|
613
|
+
|
|
614
|
+
return {
|
|
615
|
+
token,
|
|
616
|
+
status,
|
|
617
|
+
jobs,
|
|
618
|
+
gates,
|
|
619
|
+
// One gate or none. Two gates behind one token means no single declaration
|
|
620
|
+
// replaces it, and picking either would turn off half of what the token
|
|
621
|
+
// turns off while reading as a complete migration.
|
|
622
|
+
gate: gates.length === 1 ? gates[0] : null,
|
|
623
|
+
ungated,
|
|
624
|
+
};
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
/**
|
|
628
|
+
* Resolve a token to the declaration that replaces it AT ONE MOMENT.
|
|
629
|
+
*
|
|
630
|
+
* The moment is not decoration. `quality.yml` takes it as an input, every job
|
|
631
|
+
* resolves its gate at it, and a gate's registry entry names the closed set of
|
|
632
|
+
* moments it may legally be declared at — `traceability` at `push` and
|
|
633
|
+
* `pull-request` and nowhere else. So a caller running the pre-deploy set has
|
|
634
|
+
* no legal declaration for that token, and printing one anyway would be
|
|
635
|
+
* refused by `validate` AFTER the operator had already deleted the token,
|
|
636
|
+
* leaving the check running unguarded with the configuration reading migrated.
|
|
637
|
+
*
|
|
638
|
+
* Emitted by both `lisa doctor` and this file's `skip-jobs` command, from here,
|
|
639
|
+
* so the string an operator is shown cannot differ between the two.
|
|
640
|
+
* @param {string} token A `skip_jobs` token, exactly as the caller spells it.
|
|
641
|
+
* @param {string} [moment] The moment the caller passes to `quality.yml`.
|
|
642
|
+
* @returns {{token: string, status: string, moment: string, jobs: string[], gates: string[], gate: string|null, ungated: string[], declaration: string|null}} The migration for that token.
|
|
643
|
+
*/
|
|
644
|
+
export function skipJobMigration(token, moment = "pull-request") {
|
|
645
|
+
const resolved = gateForSkipJob(token);
|
|
646
|
+
if (resolved.gate === null) {
|
|
647
|
+
return { ...resolved, moment, declaration: null };
|
|
648
|
+
}
|
|
649
|
+
const permitted =
|
|
650
|
+
isMoment(moment) &&
|
|
651
|
+
(REGISTRY[resolved.gate]?.moments ?? []).includes(momentFamily(moment));
|
|
652
|
+
if (!permitted) {
|
|
653
|
+
return { ...resolved, moment, status: "moment-illegal", declaration: null };
|
|
654
|
+
}
|
|
655
|
+
return {
|
|
656
|
+
...resolved,
|
|
657
|
+
moment,
|
|
658
|
+
declaration: `"${resolved.gate}": { "${moment}": "off" }`,
|
|
659
|
+
};
|
|
660
|
+
}
|
|
661
|
+
|
|
455
662
|
/**
|
|
456
663
|
* Guarantees the platform enforces, which no task can implement.
|
|
457
664
|
*
|
|
@@ -1414,6 +1621,32 @@ function main() {
|
|
|
1414
1621
|
return;
|
|
1415
1622
|
}
|
|
1416
1623
|
|
|
1624
|
+
if (command === "skip-jobs") {
|
|
1625
|
+
// Deliberately not gated on reading .lisa.config.json: this answers "what
|
|
1626
|
+
// would replace this token", which a repository that has not migrated yet
|
|
1627
|
+
// must be able to ask. It is the surface an agent in a consumer checkout
|
|
1628
|
+
// uses, so it must work before anything has been declared.
|
|
1629
|
+
const requested = (flag("tokens") ?? "")
|
|
1630
|
+
.split(",")
|
|
1631
|
+
.filter(entry => entry !== "");
|
|
1632
|
+
const moment = flag("moment") ?? "pull-request";
|
|
1633
|
+
const resolved = (
|
|
1634
|
+
requested.length ? requested : Object.keys(SKIP_JOB_TOKENS)
|
|
1635
|
+
).map(token => skipJobMigration(token, moment));
|
|
1636
|
+
if (rest.includes("--json")) {
|
|
1637
|
+
console.log(JSON.stringify(resolved, null, 2));
|
|
1638
|
+
return;
|
|
1639
|
+
}
|
|
1640
|
+
for (const entry of resolved) {
|
|
1641
|
+
console.log(
|
|
1642
|
+
`${entry.token.padEnd(24)} ${entry.status.padEnd(15)} ${
|
|
1643
|
+
entry.declaration ?? "(no declaration — see status)"
|
|
1644
|
+
}`
|
|
1645
|
+
);
|
|
1646
|
+
}
|
|
1647
|
+
return;
|
|
1648
|
+
}
|
|
1649
|
+
|
|
1417
1650
|
if (command === "audit-config") {
|
|
1418
1651
|
const findings = auditConfigKeys(config);
|
|
1419
1652
|
for (const finding of findings) console.error(` ${finding.message}`);
|
|
@@ -1422,7 +1655,7 @@ function main() {
|
|
|
1422
1655
|
}
|
|
1423
1656
|
|
|
1424
1657
|
throw new Error(
|
|
1425
|
-
"usage: lisa-gates.mjs validate|list|needs|contexts|audit-config"
|
|
1658
|
+
"usage: lisa-gates.mjs validate|list|needs|contexts|skip-jobs|audit-config"
|
|
1426
1659
|
);
|
|
1427
1660
|
}
|
|
1428
1661
|
|
|
@@ -15,8 +15,10 @@ interface ArtifactCheck {
|
|
|
15
15
|
* nobody can account for is worth a line either way, and staying silent about a
|
|
16
16
|
* downstream edit would let a project quietly swap a guard for a stub.
|
|
17
17
|
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
18
|
+
* A third finding covers absence, but only for the universal tree. A missing
|
|
19
|
+
* stack-specific artifact means that stack does not apply here, which is not
|
|
20
|
+
* drift. A missing universal one means apply has not run, and the CI gate that
|
|
21
|
+
* calls it is not running at all.
|
|
20
22
|
* @param targetPath - Project path to inspect
|
|
21
23
|
* @param lisaRoot - Installed Lisa package root (injected by tests)
|
|
22
24
|
* @param ledger - Known-good hashes, defaulting to Lisa's shipping history
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"doctor-lisa-owned-artifacts.d.ts","sourceRoot":"","sources":["../../src/cli/doctor-lisa-owned-artifacts.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"doctor-lisa-owned-artifacts.d.ts","sourceRoot":"","sources":["../../src/cli/doctor-lisa-owned-artifacts.ts"],"names":[],"mappings":"AA0BA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kCAAkC,CAAC;AA0BnE,wEAAwE;AACxE,UAAU,aAAa;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,IAAI,GAAG,MAAM,GAAG,MAAM,CAAC;IAC/B,MAAM,EAAE,MAAM,CAAC;CAChB;AA8FD;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAsB,uBAAuB,CAC3C,UAAU,EAAE,MAAM,EAClB,QAAQ,GAAE,MAA0B,EACpC,MAAM,CAAC,EAAE,UAAU,GAClB,OAAO,CAAC,aAAa,CAAC,CAgDxB"}
|
|
@@ -18,16 +18,13 @@
|
|
|
18
18
|
import { readFile } from "node:fs/promises";
|
|
19
19
|
import * as path from "node:path";
|
|
20
20
|
import { fileURLToPath } from "node:url";
|
|
21
|
-
import * as fse from "fs-extra";
|
|
22
21
|
import { PROJECT_TYPE_ORDER } from "../core/config.js";
|
|
23
22
|
import { classifyHostCopy, mayRefreshLisaOwned, } from "../core/lisa-owned-provenance.js";
|
|
24
|
-
import { isLisaOwnedTemplate } from "../core/lisa-owned-templates.js";
|
|
25
23
|
import { isLisaSourceRepo } from "../core/self-apply.js";
|
|
26
|
-
import { listFilesRecursive } from "../utils/file-operations.js";
|
|
27
24
|
import { matchesAnyPattern, parseIgnorePatterns, } from "../utils/ignore-patterns.js";
|
|
28
25
|
import { describeResolvableCopies, } from "./doctor-lisa-owned-artifact-copies.js";
|
|
26
|
+
import { shippedByStack, UNIVERSAL_STACK, universalDestinations, } from "./doctor-lisa-owned-universal.js";
|
|
29
27
|
const CHECK_NAME = "Lisa enforcement artifacts current?";
|
|
30
|
-
const COPY_OVERWRITE = "copy-overwrite";
|
|
31
28
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
32
29
|
/**
|
|
33
30
|
* Quoted relative module specifiers — `import`/`export from`, `require()`, and
|
|
@@ -42,23 +39,6 @@ const RELATIVE_SPECIFIER = /["'`](\.\.?\/[^"'`\n]{1,4096})["'`]/g;
|
|
|
42
39
|
function defaultLisaRoot() {
|
|
43
40
|
return path.resolve(__dirname, "..", "..");
|
|
44
41
|
}
|
|
45
|
-
/**
|
|
46
|
-
* List the Lisa-owned artifacts one stack's copy-overwrite tree ships.
|
|
47
|
-
* @param lisaRoot - Installed Lisa package root
|
|
48
|
-
* @param type - Stack directory name
|
|
49
|
-
* @returns Destination/source pairs shipped by that stack
|
|
50
|
-
*/
|
|
51
|
-
async function shippedByStack(lisaRoot, type) {
|
|
52
|
-
const directory = path.join(lisaRoot, type, COPY_OVERWRITE);
|
|
53
|
-
if (!(await fse.pathExists(directory)))
|
|
54
|
-
return [];
|
|
55
|
-
return (await listFilesRecursive(directory))
|
|
56
|
-
.map(source => [
|
|
57
|
-
path.relative(directory, source).split(path.sep).join("/"),
|
|
58
|
-
source,
|
|
59
|
-
])
|
|
60
|
-
.filter(([destination]) => isLisaOwnedTemplate(destination));
|
|
61
|
-
}
|
|
62
42
|
/**
|
|
63
43
|
* Collect every shipped Lisa-owned artifact, keyed by its destination path.
|
|
64
44
|
*
|
|
@@ -69,7 +49,7 @@ async function shippedByStack(lisaRoot, type) {
|
|
|
69
49
|
* @returns Destination path to the shipped source files that produce it
|
|
70
50
|
*/
|
|
71
51
|
async function shippedArtifacts(lisaRoot) {
|
|
72
|
-
const shipped = (await Promise.all([
|
|
52
|
+
const shipped = (await Promise.all([UNIVERSAL_STACK, ...PROJECT_TYPE_ORDER].map(async (type) => shippedByStack(lisaRoot, type)))).flat();
|
|
73
53
|
const destinations = [
|
|
74
54
|
...new Set(shipped.map(([destination]) => destination)),
|
|
75
55
|
];
|
|
@@ -123,8 +103,10 @@ function reExportsShippedTemplate(installed, installedPath, sources) {
|
|
|
123
103
|
* nobody can account for is worth a line either way, and staying silent about a
|
|
124
104
|
* downstream edit would let a project quietly swap a guard for a stub.
|
|
125
105
|
*
|
|
126
|
-
*
|
|
127
|
-
*
|
|
106
|
+
* A third finding covers absence, but only for the universal tree. A missing
|
|
107
|
+
* stack-specific artifact means that stack does not apply here, which is not
|
|
108
|
+
* drift. A missing universal one means apply has not run, and the CI gate that
|
|
109
|
+
* calls it is not running at all.
|
|
128
110
|
* @param targetPath - Project path to inspect
|
|
129
111
|
* @param lisaRoot - Installed Lisa package root (injected by tests)
|
|
130
112
|
* @param ledger - Known-good hashes, defaulting to Lisa's shipping history
|
|
@@ -132,6 +114,7 @@ function reExportsShippedTemplate(installed, installedPath, sources) {
|
|
|
132
114
|
*/
|
|
133
115
|
export async function checkLisaOwnedArtifacts(targetPath, lisaRoot = defaultLisaRoot(), ledger) {
|
|
134
116
|
const shipped = await shippedArtifacts(lisaRoot);
|
|
117
|
+
const universal = await universalDestinations(lisaRoot);
|
|
135
118
|
if (shipped.size === 0) {
|
|
136
119
|
return {
|
|
137
120
|
name: CHECK_NAME,
|
|
@@ -146,7 +129,7 @@ export async function checkLisaOwnedArtifacts(targetPath, lisaRoot = defaultLisa
|
|
|
146
129
|
// unless the target's package.json is Lisa itself. A host must not be able to
|
|
147
130
|
// swap a guard for a thin re-export and have doctor call it current.
|
|
148
131
|
const selfHost = await isLisaSourceRepo(targetPath);
|
|
149
|
-
const results = await Promise.all([...shipped].map(async ([destination, sources]) => assessArtifact(targetPath, lisaRoot, destination, sources, ignorePatterns, selfHost, ledger)));
|
|
132
|
+
const results = await Promise.all([...shipped].map(async ([destination, sources]) => assessArtifact(targetPath, lisaRoot, destination, sources, ignorePatterns, selfHost, universal.has(destination), ledger)));
|
|
150
133
|
const assessments = results.filter((item) => item !== undefined);
|
|
151
134
|
return summarise(assessments
|
|
152
135
|
.map(item => item.finding)
|
|
@@ -162,19 +145,16 @@ export async function checkLisaOwnedArtifacts(targetPath, lisaRoot = defaultLisa
|
|
|
162
145
|
* @param sources - Absolute shipped package variants
|
|
163
146
|
* @param ignorePatterns - Parsed .lisaignore patterns
|
|
164
147
|
* @param selfHost - Whether the target is Lisa's own source repository
|
|
148
|
+
* @param universal - Whether every project receives this destination
|
|
165
149
|
* @param ledger - Known-good hashes, defaulting to Lisa's shipping history
|
|
166
|
-
* @returns Per-artifact assessment, or undefined when
|
|
150
|
+
* @returns Per-artifact assessment, or undefined when nothing is reportable
|
|
167
151
|
*/
|
|
168
|
-
async function assessArtifact(targetPath, lisaRoot, destination, sources, ignorePatterns, selfHost, ledger) {
|
|
152
|
+
async function assessArtifact(targetPath, lisaRoot, destination, sources, ignorePatterns, selfHost, universal, ledger) {
|
|
169
153
|
const installedPath = path.join(targetPath, destination);
|
|
170
154
|
const installed = await readFile(installedPath).catch(() => undefined);
|
|
171
155
|
const ignored = matchesAnyPattern(destination, ignorePatterns);
|
|
172
156
|
if (installed === undefined) {
|
|
173
|
-
return ignored
|
|
174
|
-
? {
|
|
175
|
-
finding: [destination, "ignored"],
|
|
176
|
-
}
|
|
177
|
-
: undefined;
|
|
157
|
+
return assessAbsent(destination, ignored, universal, selfHost);
|
|
178
158
|
}
|
|
179
159
|
const multiCopy = await describeResolvableCopies(lisaRoot, destination, installed, sources);
|
|
180
160
|
if (ignored)
|
|
@@ -188,6 +168,35 @@ async function assessArtifact(targetPath, lisaRoot, destination, sources, ignore
|
|
|
188
168
|
const outdated = await isProvablyStale(installed, destination, sources, ledger);
|
|
189
169
|
return withMultiCopy([destination, outdated ? "stale" : "modified"], multiCopy);
|
|
190
170
|
}
|
|
171
|
+
/**
|
|
172
|
+
* Assess a Lisa-owned artifact the project does not have.
|
|
173
|
+
*
|
|
174
|
+
* An absent stack-specific artifact means that stack does not apply here, which
|
|
175
|
+
* is not drift — that exemption is why absence used to report nothing at all.
|
|
176
|
+
* It over-reached: a universal artifact has no stack it does not apply to, so
|
|
177
|
+
* its absence means apply never ran and the CI gate that calls it is not
|
|
178
|
+
* running. Measured on `scripts/lisa-floor-collisions.mjs` (#2731), whose job
|
|
179
|
+
* exited 0 on the missing script — green, having examined nothing, with doctor
|
|
180
|
+
* silent too. Fixing only the job turns a silent pass into a red nobody has
|
|
181
|
+
* been told how to clear.
|
|
182
|
+
*
|
|
183
|
+
* Never for Lisa's own repository: it is the source of these artifacts, not a
|
|
184
|
+
* consumer, and installs only the few it runs on itself, so "apply never ran"
|
|
185
|
+
* is a category error there.
|
|
186
|
+
* @param destination - Project-relative artifact destination
|
|
187
|
+
* @param ignored - Whether .lisaignore names this destination
|
|
188
|
+
* @param universal - Whether every project receives this destination
|
|
189
|
+
* @param selfHost - Whether the target is Lisa's own source repository
|
|
190
|
+
* @returns Per-artifact assessment, or undefined when absence proves nothing
|
|
191
|
+
*/
|
|
192
|
+
function assessAbsent(destination, ignored, universal, selfHost) {
|
|
193
|
+
if (ignored) {
|
|
194
|
+
return { finding: [destination, "ignored"] };
|
|
195
|
+
}
|
|
196
|
+
return universal && !selfHost
|
|
197
|
+
? { finding: [destination, "missing"] }
|
|
198
|
+
: undefined;
|
|
199
|
+
}
|
|
191
200
|
/**
|
|
192
201
|
* Attach optional multi-copy provenance without materialising undefined fields.
|
|
193
202
|
* @param finding - Artifact finding tuple
|
|
@@ -211,10 +220,12 @@ function summarise(findings, multiCopies) {
|
|
|
211
220
|
const stale = pick("stale");
|
|
212
221
|
const modified = pick("modified");
|
|
213
222
|
const ignored = pick("ignored");
|
|
223
|
+
const missing = pick("missing");
|
|
214
224
|
const copyDisagreements = multiCopies.filter(copy => copy.disagrees);
|
|
215
225
|
const copyReports = renderCopyReports(multiCopies);
|
|
216
226
|
const warningFree = stale.length === 0 &&
|
|
217
227
|
modified.length === 0 &&
|
|
228
|
+
missing.length === 0 &&
|
|
218
229
|
copyDisagreements.length === 0;
|
|
219
230
|
if (warningFree) {
|
|
220
231
|
// Named rather than folded into the pass. A guard excluded by
|
|
@@ -231,6 +242,9 @@ function summarise(findings, multiCopies) {
|
|
|
231
242
|
ignored.length > 0
|
|
232
243
|
? `${ignored.length} not assessed (.lisaignore): ${ignored.join(", ")}`
|
|
233
244
|
: "",
|
|
245
|
+
missing.length > 0
|
|
246
|
+
? `Lisa-owned guards every project receives are not installed, so the CI gates that call them run against nothing (run \`npx lisa apply .\` to install them): ${missing.join(", ")}`
|
|
247
|
+
: "",
|
|
234
248
|
stale.length > 0
|
|
235
249
|
? `Outdated Lisa-owned guards (run \`npx lisa apply .\` to refresh): ${stale.join(", ")}`
|
|
236
250
|
: "",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"doctor-lisa-owned-artifacts.js","sourceRoot":"","sources":["../../src/cli/doctor-lisa-owned-artifacts.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AACH,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAClC,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;
|
|
1
|
+
{"version":3,"file":"doctor-lisa-owned-artifacts.js","sourceRoot":"","sources":["../../src/cli/doctor-lisa-owned-artifacts.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/** Directory name of the strategy tree these artifacts are shipped from. */
|
|
2
|
+
export declare const COPY_OVERWRITE = "copy-overwrite";
|
|
3
|
+
/**
|
|
4
|
+
* The stack tree every project receives regardless of detected type.
|
|
5
|
+
*
|
|
6
|
+
* `Lisa.processProjectType("all")` runs unconditionally and every downstream
|
|
7
|
+
* loop is `["all", ...detectedTypes]`, so an artifact shipped here has no stack
|
|
8
|
+
* it legitimately does not apply to.
|
|
9
|
+
*/
|
|
10
|
+
export declare const UNIVERSAL_STACK = "all";
|
|
11
|
+
/** One shipped Lisa-owned artifact and the destination it installs to. */
|
|
12
|
+
export type ShippedArtifact = readonly [destination: string, source: string];
|
|
13
|
+
/**
|
|
14
|
+
* List the Lisa-owned artifacts one stack's copy-overwrite tree ships.
|
|
15
|
+
* @param lisaRoot - Installed Lisa package root
|
|
16
|
+
* @param type - Stack directory name
|
|
17
|
+
* @returns Destination/source pairs shipped by that stack
|
|
18
|
+
*/
|
|
19
|
+
export declare function shippedByStack(lisaRoot: string, type: string): Promise<readonly ShippedArtifact[]>;
|
|
20
|
+
/**
|
|
21
|
+
* Destinations shipped by the universal tree, which every project receives.
|
|
22
|
+
* @param lisaRoot - Installed Lisa package root
|
|
23
|
+
* @returns Destination paths no project can legitimately be without
|
|
24
|
+
*/
|
|
25
|
+
export declare function universalDestinations(lisaRoot: string): Promise<ReadonlySet<string>>;
|
|
26
|
+
//# sourceMappingURL=doctor-lisa-owned-universal.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"doctor-lisa-owned-universal.d.ts","sourceRoot":"","sources":["../../src/cli/doctor-lisa-owned-universal.ts"],"names":[],"mappings":"AAgBA,4EAA4E;AAC5E,eAAO,MAAM,cAAc,mBAAmB,CAAC;AAE/C;;;;;;GAMG;AACH,eAAO,MAAM,eAAe,QAAQ,CAAC;AAErC,0EAA0E;AAC1E,MAAM,MAAM,eAAe,GAAG,SAAS,CAAC,WAAW,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;AAE7E;;;;;GAKG;AACH,wBAAsB,cAAc,CAClC,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,SAAS,eAAe,EAAE,CAAC,CAYrC;AAED;;;;GAIG;AACH,wBAAsB,qBAAqB,CACzC,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC,CAM9B"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which Lisa-owned artifacts every project receives, whatever stack it is.
|
|
3
|
+
*
|
|
4
|
+
* Kept separate from `doctor-lisa-owned-artifacts.ts` for the max-lines budget,
|
|
5
|
+
* the same way the safety-net fixtures are split. The distinction it encodes is
|
|
6
|
+
* small but load-bearing: absence of a STACK artifact proves nothing, because
|
|
7
|
+
* that stack may simply not apply here, while absence of a UNIVERSAL one means
|
|
8
|
+
* apply never ran and the CI gate that calls it is not running at all.
|
|
9
|
+
* @module cli/doctor-lisa-owned-universal
|
|
10
|
+
*/
|
|
11
|
+
import * as path from "node:path";
|
|
12
|
+
import * as fse from "fs-extra";
|
|
13
|
+
import { isLisaOwnedTemplate } from "../core/lisa-owned-templates.js";
|
|
14
|
+
import { listFilesRecursive } from "../utils/file-operations.js";
|
|
15
|
+
/** Directory name of the strategy tree these artifacts are shipped from. */
|
|
16
|
+
export const COPY_OVERWRITE = "copy-overwrite";
|
|
17
|
+
/**
|
|
18
|
+
* The stack tree every project receives regardless of detected type.
|
|
19
|
+
*
|
|
20
|
+
* `Lisa.processProjectType("all")` runs unconditionally and every downstream
|
|
21
|
+
* loop is `["all", ...detectedTypes]`, so an artifact shipped here has no stack
|
|
22
|
+
* it legitimately does not apply to.
|
|
23
|
+
*/
|
|
24
|
+
export const UNIVERSAL_STACK = "all";
|
|
25
|
+
/**
|
|
26
|
+
* List the Lisa-owned artifacts one stack's copy-overwrite tree ships.
|
|
27
|
+
* @param lisaRoot - Installed Lisa package root
|
|
28
|
+
* @param type - Stack directory name
|
|
29
|
+
* @returns Destination/source pairs shipped by that stack
|
|
30
|
+
*/
|
|
31
|
+
export async function shippedByStack(lisaRoot, type) {
|
|
32
|
+
const directory = path.join(lisaRoot, type, COPY_OVERWRITE);
|
|
33
|
+
if (!(await fse.pathExists(directory)))
|
|
34
|
+
return [];
|
|
35
|
+
return (await listFilesRecursive(directory))
|
|
36
|
+
.map(source => [
|
|
37
|
+
path.relative(directory, source).split(path.sep).join("/"),
|
|
38
|
+
source,
|
|
39
|
+
])
|
|
40
|
+
.filter(([destination]) => isLisaOwnedTemplate(destination));
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Destinations shipped by the universal tree, which every project receives.
|
|
44
|
+
* @param lisaRoot - Installed Lisa package root
|
|
45
|
+
* @returns Destination paths no project can legitimately be without
|
|
46
|
+
*/
|
|
47
|
+
export async function universalDestinations(lisaRoot) {
|
|
48
|
+
return new Set((await shippedByStack(lisaRoot, UNIVERSAL_STACK)).map(([destination]) => destination));
|
|
49
|
+
}
|
|
50
|
+
//# sourceMappingURL=doctor-lisa-owned-universal.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"doctor-lisa-owned-universal.js","sourceRoot":"","sources":["../../src/cli/doctor-lisa-owned-universal.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAClC,OAAO,KAAK,GAAG,MAAM,UAAU,CAAC;AAEhC,OAAO,EAAE,mBAAmB,EAAE,MAAM,iCAAiC,CAAC;AACtE,OAAO,EAAE,kBAAkB,EAAE,MAAM,6BAA6B,CAAC;AAEjE,4EAA4E;AAC5E,MAAM,CAAC,MAAM,cAAc,GAAG,gBAAgB,CAAC;AAE/C;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG,KAAK,CAAC;AAKrC;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CAClC,QAAgB,EAChB,IAAY;IAEZ,MAAM,SAAS,GAAG,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,IAAI,EAAE,cAAc,CAAC,CAAC;IAC5D,IAAI,CAAC,CAAC,MAAM,GAAG,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC;QAAE,OAAO,EAAE,CAAC;IAClD,OAAO,CAAC,MAAM,kBAAkB,CAAC,SAAS,CAAC,CAAC;SACzC,GAAG,CACF,MAAM,CAAC,EAAE,CACP;QACE,IAAI,CAAC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC;QAC1D,MAAM;KACE,CACb;SACA,MAAM,CAAC,CAAC,CAAC,WAAW,CAAC,EAAE,EAAE,CAAC,mBAAmB,CAAC,WAAW,CAAC,CAAC,CAAC;AACjE,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,qBAAqB,CACzC,QAAgB;IAEhB,OAAO,IAAI,GAAG,CACZ,CAAC,MAAM,cAAc,CAAC,QAAQ,EAAE,eAAe,CAAC,CAAC,CAAC,GAAG,CACnD,CAAC,CAAC,WAAW,CAAC,EAAE,EAAE,CAAC,WAAW,CAC/B,CACF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { DoctorCheck } from "./doctor.js";
|
|
2
|
+
/** One token's migration, exactly as the shipped registry resolves it. */
|
|
3
|
+
export interface SkipJobToken {
|
|
4
|
+
/** The token as the caller spells it, including any damaging whitespace. */
|
|
5
|
+
readonly token: string;
|
|
6
|
+
/** One of the registry's `SKIP_JOB_STATUS` values. */
|
|
7
|
+
readonly status: string;
|
|
8
|
+
/** The gate that replaces it, or null when there is none. */
|
|
9
|
+
readonly gate: string | null;
|
|
10
|
+
/** The `quality.yml` jobs the token suppresses. */
|
|
11
|
+
readonly jobs: readonly string[];
|
|
12
|
+
/** Suppressed jobs with no gate — these keep running after the migration. */
|
|
13
|
+
readonly ungated: readonly string[];
|
|
14
|
+
/** The `.lisa.config.json` fragment to write, or null when none exists. */
|
|
15
|
+
readonly declaration: string | null;
|
|
16
|
+
}
|
|
17
|
+
/** One caller workflow that still passes `skip_jobs`. */
|
|
18
|
+
export interface SkipJobCaller {
|
|
19
|
+
/** Workflow file, relative to the project root. */
|
|
20
|
+
readonly file: string;
|
|
21
|
+
/** The moment that caller resolves gates at. */
|
|
22
|
+
readonly moment: string;
|
|
23
|
+
/** Every token it passes, in the order it passes them. */
|
|
24
|
+
readonly tokens: readonly SkipJobToken[];
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Every caller in a project that still passes `skip_jobs`, with its migration.
|
|
28
|
+
* @param targetPath - Project root
|
|
29
|
+
* @returns One entry per `skip_jobs` input carrying at least one token
|
|
30
|
+
*/
|
|
31
|
+
export declare function skipJobCallers(targetPath: string): Promise<SkipJobCaller[]>;
|
|
32
|
+
/**
|
|
33
|
+
* The operator-readable line for one token.
|
|
34
|
+
* @param entry - The token's resolved migration
|
|
35
|
+
* @returns One line naming the edit, or naming why there is none
|
|
36
|
+
*/
|
|
37
|
+
export declare function describeToken(entry: SkipJobToken): string;
|
|
38
|
+
/**
|
|
39
|
+
* Report the gate declarations that replace a caller's `skip_jobs` tokens.
|
|
40
|
+
*
|
|
41
|
+
* Reports only. `ci.yml` ships create-only, so `lisa apply` never refreshes it
|
|
42
|
+
* and the migration is a consumer-side edit either way — but the reason this
|
|
43
|
+
* does not write is the one in the module docstring, not that one.
|
|
44
|
+
* @param targetPath - Project path to inspect
|
|
45
|
+
* @returns Doctor check result
|
|
46
|
+
*/
|
|
47
|
+
export declare function checkSkipJobsMigration(targetPath: string): Promise<DoctorCheck>;
|
|
48
|
+
//# sourceMappingURL=doctor-skip-jobs-migration.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"doctor-skip-jobs-migration.d.ts","sourceRoot":"","sources":["../../src/cli/doctor-skip-jobs-migration.ts"],"names":[],"mappings":"AA4CA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAgB/C,0EAA0E;AAC1E,MAAM,WAAW,YAAY;IAC3B,4EAA4E;IAC5E,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,sDAAsD;IACtD,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,6DAA6D;IAC7D,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,mDAAmD;IACnD,QAAQ,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,CAAC;IACjC,6EAA6E;IAC7E,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;IACpC,2EAA2E;IAC3E,QAAQ,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;CACrC;AAED,yDAAyD;AACzD,MAAM,WAAW,aAAa;IAC5B,mDAAmD;IACnD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,gDAAgD;IAChD,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,0DAA0D;IAC1D,QAAQ,CAAC,MAAM,EAAE,SAAS,YAAY,EAAE,CAAC;CAC1C;AA6KD;;;;GAIG;AACH,wBAAsB,cAAc,CAClC,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,aAAa,EAAE,CAAC,CAQ1B;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,YAAY,GAAG,MAAM,CA4BzD;AAED;;;;;;;;GAQG;AACH,wBAAsB,sBAAsB,CAC1C,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,WAAW,CAAC,CAoCtB"}
|