mason-context 0.19.0 → 0.19.2
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/CHANGELOG.md +19 -0
- package/dist/mason-audit.js +524 -324
- package/dist/mason-audit.js.map +1 -1
- package/dist/mason-auto.js +1099 -729
- package/dist/mason-auto.js.map +1 -1
- package/dist/mason-drift.js +50 -0
- package/dist/mason-drift.js.map +1 -1
- package/dist/mason-hook.js.map +1 -1
- package/dist/mason-mcp.js +1450 -1069
- package/dist/mason-mcp.js.map +1 -1
- package/dist/mason-review.js +50 -0
- package/dist/mason-review.js.map +1 -1
- package/dist/mason-runtime.js +157 -54
- package/dist/mason-runtime.js.map +1 -1
- package/dist/mason.js +1208 -784
- package/dist/mason.js.map +1 -1
- package/package.json +1 -1
package/dist/mason.js
CHANGED
|
@@ -96,6 +96,26 @@ var init_git_read = __esm({
|
|
|
96
96
|
}
|
|
97
97
|
});
|
|
98
98
|
|
|
99
|
+
// src/audit/types.ts
|
|
100
|
+
var types_exports = {};
|
|
101
|
+
__export(types_exports, {
|
|
102
|
+
ALL_CHECKS: () => ALL_CHECKS
|
|
103
|
+
});
|
|
104
|
+
var ALL_CHECKS;
|
|
105
|
+
var init_types = __esm({
|
|
106
|
+
"src/audit/types.ts"() {
|
|
107
|
+
"use strict";
|
|
108
|
+
ALL_CHECKS = [
|
|
109
|
+
"deleted-reference",
|
|
110
|
+
"new-module",
|
|
111
|
+
"stale-count",
|
|
112
|
+
"dead-command",
|
|
113
|
+
"deps-changed",
|
|
114
|
+
"decision-anchor-drift"
|
|
115
|
+
];
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
|
|
99
119
|
// src/utils/files.ts
|
|
100
120
|
import fs from "fs/promises";
|
|
101
121
|
import { constants } from "fs";
|
|
@@ -140,6 +160,27 @@ async function loadProjectConfig(root) {
|
|
|
140
160
|
}
|
|
141
161
|
config[key] = value[key];
|
|
142
162
|
}
|
|
163
|
+
if (value.audit !== void 0) {
|
|
164
|
+
const audit = value.audit;
|
|
165
|
+
if (!audit || typeof audit !== "object" || Array.isArray(audit)) throw new Error("Configuration audit must be an object");
|
|
166
|
+
const patterns = (raw2) => {
|
|
167
|
+
if (!Array.isArray(raw2) || raw2.length > 100 || !raw2.every((s) => typeof s === "string" && normalizeRepoPath(s) === s && !s.startsWith("!") && !/[\x00-\x1f]/.test(s))) {
|
|
168
|
+
throw new Error("Audit patterns must be at most 100 repository-relative globs without traversal or negation");
|
|
169
|
+
}
|
|
170
|
+
return raw2;
|
|
171
|
+
};
|
|
172
|
+
config.audit = {};
|
|
173
|
+
if (audit.include !== void 0) config.audit.include = patterns(audit.include);
|
|
174
|
+
if (audit.exclude !== void 0) {
|
|
175
|
+
if (!audit.exclude || typeof audit.exclude !== "object" || Array.isArray(audit.exclude)) throw new Error("audit.exclude must map check names to patterns");
|
|
176
|
+
const { ALL_CHECKS: ALL_CHECKS2 } = await Promise.resolve().then(() => (init_types(), types_exports));
|
|
177
|
+
config.audit.exclude = {};
|
|
178
|
+
for (const [check, globs] of Object.entries(audit.exclude)) {
|
|
179
|
+
if (!ALL_CHECKS2.includes(check)) throw new Error("Unknown audit check: " + check);
|
|
180
|
+
config.audit.exclude[check] = patterns(globs);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
143
184
|
return config;
|
|
144
185
|
} catch (error) {
|
|
145
186
|
if (error.code === "ENOENT") return {};
|
|
@@ -394,8 +435,229 @@ var init_store_lock = __esm({
|
|
|
394
435
|
}
|
|
395
436
|
});
|
|
396
437
|
|
|
397
|
-
// src/
|
|
438
|
+
// src/audit/findings.ts
|
|
439
|
+
import { createHash } from "crypto";
|
|
398
440
|
import { z } from "zod";
|
|
441
|
+
function findingId(finding) {
|
|
442
|
+
const e = finding.evidence;
|
|
443
|
+
let key;
|
|
444
|
+
switch (e.kind) {
|
|
445
|
+
case "missing-path":
|
|
446
|
+
key = e.resolvedPath && e.resolvedPath !== e.claimed ? [e.claimed, e.resolvedPath] : e.claimed;
|
|
447
|
+
break;
|
|
448
|
+
case "unmentioned-dir":
|
|
449
|
+
key = e.dir;
|
|
450
|
+
break;
|
|
451
|
+
case "count-mismatch":
|
|
452
|
+
key = [e.unit.replace(/s$/, ""), e.countedFrom];
|
|
453
|
+
break;
|
|
454
|
+
case "missing-script":
|
|
455
|
+
key = e.scope && e.scope.directory !== "." ? [e.scriptName, e.scope.directory] : e.scriptName;
|
|
456
|
+
break;
|
|
457
|
+
case "doc-behind-manifests":
|
|
458
|
+
key = null;
|
|
459
|
+
break;
|
|
460
|
+
case "decision-anchor":
|
|
461
|
+
key = [e.decisionId, e.provenance?.revision, e.provenance?.approval];
|
|
462
|
+
break;
|
|
463
|
+
}
|
|
464
|
+
return digest([finding.type, finding.anchor.doc, key]);
|
|
465
|
+
}
|
|
466
|
+
var digest, checkSchema, commitSchema, anchorSchema, count, scopeSchema, evidenceSchema, findingSchema, issueSchema, advisorySchema, checkResultSchema;
|
|
467
|
+
var init_findings = __esm({
|
|
468
|
+
"src/audit/findings.ts"() {
|
|
469
|
+
"use strict";
|
|
470
|
+
digest = (value) => createHash("sha256").update(JSON.stringify(value)).digest("hex");
|
|
471
|
+
checkSchema = z.enum(["deleted-reference", "new-module", "stale-count", "dead-command", "deps-changed", "decision-anchor-drift"]);
|
|
472
|
+
commitSchema = z.object({ hash: z.string().regex(/^[a-f0-9]{40,64}$/), date: z.string(), subject: z.string() });
|
|
473
|
+
anchorSchema = z.object({ doc: z.string(), line: z.number().int().positive().nullable(), excerpt: z.string().nullable() });
|
|
474
|
+
count = z.number().int().nonnegative();
|
|
475
|
+
scopeSchema = z.object({
|
|
476
|
+
basis: z.enum(["repository", "document", "document-link", "explicit"]),
|
|
477
|
+
directory: z.string(),
|
|
478
|
+
candidates: z.array(z.string())
|
|
479
|
+
});
|
|
480
|
+
evidenceSchema = z.discriminatedUnion("kind", [
|
|
481
|
+
z.object({
|
|
482
|
+
kind: z.literal("missing-path"),
|
|
483
|
+
claimed: z.string(),
|
|
484
|
+
renamedTo: z.string().nullable(),
|
|
485
|
+
deletedInCommit: commitSchema.nullable(),
|
|
486
|
+
everTracked: z.boolean(),
|
|
487
|
+
parentDirExists: z.boolean(),
|
|
488
|
+
resolvedPath: z.string().optional(),
|
|
489
|
+
scope: scopeSchema.optional()
|
|
490
|
+
}),
|
|
491
|
+
z.object({
|
|
492
|
+
kind: z.literal("unmentioned-dir"),
|
|
493
|
+
dir: z.string(),
|
|
494
|
+
sourceFileCount: count,
|
|
495
|
+
firstCommit: commitSchema.nullable(),
|
|
496
|
+
checkedDocs: z.array(z.string())
|
|
497
|
+
}),
|
|
498
|
+
z.object({
|
|
499
|
+
kind: z.literal("count-mismatch"),
|
|
500
|
+
claimed: count,
|
|
501
|
+
actual: count,
|
|
502
|
+
unit: z.string(),
|
|
503
|
+
countedFrom: z.string(),
|
|
504
|
+
members: z.array(z.string())
|
|
505
|
+
}),
|
|
506
|
+
z.object({
|
|
507
|
+
kind: z.literal("missing-script"),
|
|
508
|
+
scriptName: z.string(),
|
|
509
|
+
invocation: z.string(),
|
|
510
|
+
manifestsChecked: z.array(z.string()),
|
|
511
|
+
availableScripts: z.array(z.string()),
|
|
512
|
+
scope: scopeSchema.optional()
|
|
513
|
+
}),
|
|
514
|
+
z.object({
|
|
515
|
+
kind: z.literal("doc-behind-manifests"),
|
|
516
|
+
docLastCommit: commitSchema,
|
|
517
|
+
manifestCommits: z.array(commitSchema.extend({ files: z.array(z.string()) })),
|
|
518
|
+
totalCommits: count,
|
|
519
|
+
matches: z.array(z.object({ dependency: z.string(), manifest: z.string(), before: z.string().nullable(), after: z.string().nullable(), line: z.number().int().positive(), excerpt: z.string() })).optional(),
|
|
520
|
+
matchingIncomplete: z.boolean().optional()
|
|
521
|
+
}),
|
|
522
|
+
z.object({
|
|
523
|
+
kind: z.literal("decision-anchor"),
|
|
524
|
+
decisionId: z.string(),
|
|
525
|
+
title: z.string(),
|
|
526
|
+
changedFiles: z.array(z.string()),
|
|
527
|
+
refreshedHash: z.string(),
|
|
528
|
+
provenance: z.object({}).passthrough().optional()
|
|
529
|
+
})
|
|
530
|
+
]);
|
|
531
|
+
findingSchema = z.object({ message: z.string(), anchor: anchorSchema, evidence: evidenceSchema });
|
|
532
|
+
issueSchema = findingSchema.extend({
|
|
533
|
+
type: z.enum(["deleted-reference", "new-module", "stale-count", "dead-command"]),
|
|
534
|
+
confidence: z.enum(["certain", "likely"])
|
|
535
|
+
});
|
|
536
|
+
advisorySchema = findingSchema.extend({ type: checkSchema, resolution: z.enum(["recheck", "informational"]).optional() });
|
|
537
|
+
checkResultSchema = z.object({
|
|
538
|
+
issues: z.array(issueSchema),
|
|
539
|
+
advisories: z.array(advisorySchema),
|
|
540
|
+
suppressedAdvisories: z.array(advisorySchema).optional(),
|
|
541
|
+
skipped: z.array(z.object({ check: z.string(), reason: z.string(), doc: z.string().optional() }))
|
|
542
|
+
});
|
|
543
|
+
}
|
|
544
|
+
});
|
|
545
|
+
|
|
546
|
+
// src/automation/notifications.ts
|
|
547
|
+
import { z as z2 } from "zod";
|
|
548
|
+
function condition(finding) {
|
|
549
|
+
const source = finding.current ?? finding.original;
|
|
550
|
+
const e = source.evidence;
|
|
551
|
+
const id = e.kind === "decision-anchor" ? digest([source.type, source.anchor.doc, e.decisionId]) : finding.id;
|
|
552
|
+
const result = { level: "confidence" in source ? 2 : 1, facts: [] };
|
|
553
|
+
switch (e.kind) {
|
|
554
|
+
case "count-mismatch":
|
|
555
|
+
result.magnitude = Math.abs(e.actual - e.claimed);
|
|
556
|
+
break;
|
|
557
|
+
case "unmentioned-dir":
|
|
558
|
+
result.magnitude = e.sourceFileCount;
|
|
559
|
+
break;
|
|
560
|
+
case "decision-anchor":
|
|
561
|
+
result.facts = [...e.changedFiles].sort();
|
|
562
|
+
break;
|
|
563
|
+
case "doc-behind-manifests":
|
|
564
|
+
result.facts = (e.matches ?? []).map((m) => JSON.stringify([m.manifest, m.dependency, m.after])).sort();
|
|
565
|
+
break;
|
|
566
|
+
}
|
|
567
|
+
return { id, condition: result };
|
|
568
|
+
}
|
|
569
|
+
function notificationConditions(findings) {
|
|
570
|
+
const active = findings.filter((f) => f.status !== "resolved");
|
|
571
|
+
const currentIds = new Set(active.filter((f) => f.current).map((f) => condition(f).id));
|
|
572
|
+
const result = {};
|
|
573
|
+
for (const f of active) {
|
|
574
|
+
const value = condition(f);
|
|
575
|
+
if (!f.current && currentIds.has(value.id)) continue;
|
|
576
|
+
const previous = result[value.id];
|
|
577
|
+
result[value.id] = previous ? {
|
|
578
|
+
level: Math.max(previous.level, value.condition.level),
|
|
579
|
+
facts: [.../* @__PURE__ */ new Set([...previous.facts, ...value.condition.facts])].sort(),
|
|
580
|
+
...previous.magnitude !== void 0 || value.condition.magnitude !== void 0 ? { magnitude: Math.max(previous.magnitude ?? 0, value.condition.magnitude ?? 0) } : {}
|
|
581
|
+
} : value.condition;
|
|
582
|
+
}
|
|
583
|
+
return result;
|
|
584
|
+
}
|
|
585
|
+
function createNotificationState(findings, head) {
|
|
586
|
+
return { version: 1, baselineHead: head, baseline: notificationConditions(findings), delivered: {}, cleared: [] };
|
|
587
|
+
}
|
|
588
|
+
function worsened(current, previous) {
|
|
589
|
+
return !previous || current.level > previous.level || (current.magnitude ?? 0) > (previous.magnitude ?? 0) || current.facts.some((fact) => !previous.facts.includes(fact));
|
|
590
|
+
}
|
|
591
|
+
function completionFindings(state, findings) {
|
|
592
|
+
const current = notificationConditions(findings);
|
|
593
|
+
const cleared = new Set(state.cleared);
|
|
594
|
+
const resolvedIds = new Set(findings.filter((f) => f.status === "resolved").map((f) => condition(f).id));
|
|
595
|
+
for (const id of Object.keys(state.baseline)) if (resolvedIds.has(id) && !current[id]) cleared.add(id);
|
|
596
|
+
state.cleared = [...cleared];
|
|
597
|
+
const selected = [];
|
|
598
|
+
const selectedIds = /* @__PURE__ */ new Set();
|
|
599
|
+
const delivered = { ...state.delivered };
|
|
600
|
+
for (const finding of findings) {
|
|
601
|
+
const value = condition(finding);
|
|
602
|
+
const previous = state.delivered[value.id];
|
|
603
|
+
value.condition = current[value.id] ?? value.condition;
|
|
604
|
+
if (finding.status === "unverified" || !finding.current && finding.status !== "resolved") {
|
|
605
|
+
continue;
|
|
606
|
+
}
|
|
607
|
+
if (finding.status === "resolved") {
|
|
608
|
+
if (!current[value.id]) delete delivered[value.id];
|
|
609
|
+
continue;
|
|
610
|
+
}
|
|
611
|
+
const eligible = cleared.has(value.id) || worsened(value.condition, state.baseline[value.id]);
|
|
612
|
+
if (!eligible) continue;
|
|
613
|
+
if (!selectedIds.has(value.id) && worsened(value.condition, previous)) {
|
|
614
|
+
selectedIds.add(value.id);
|
|
615
|
+
const source = finding.current;
|
|
616
|
+
selected.push(source.evidence.kind === "decision-anchor" ? { ...finding, current: { ...source, evidence: { ...source.evidence, changedFiles: value.condition.facts } } } : finding);
|
|
617
|
+
if (selected.length <= MAX_COMPLETION_FINDINGS) delivered[value.id] = value.condition;
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
state.delivered = delivered;
|
|
621
|
+
return selected;
|
|
622
|
+
}
|
|
623
|
+
function completionSummary(findings, reportPath) {
|
|
624
|
+
if (!findings.length) return null;
|
|
625
|
+
const safe = (value) => value.replace(/[\u0000-\u001f\u007f-\u009f]/g, " ").slice(0, 250);
|
|
626
|
+
return [
|
|
627
|
+
`Mason: ${findings.length} new or worsened finding(s) since the retained baseline.`,
|
|
628
|
+
...findings.slice(0, MAX_COMPLETION_FINDINGS).map((f) => {
|
|
629
|
+
const source = f.current ?? f.original;
|
|
630
|
+
const detail = source.evidence.kind === "decision-anchor" ? `Changes to ${source.evidence.changedFiles.join(", ")} may affect decision "${source.evidence.title}". Inspect the diff if relevant; acceptance is not required.` : source.message;
|
|
631
|
+
return `[${f.status === "unresolved" ? "issue" : "advisory"}] ${safe(source.anchor.doc)}: ${safe(detail)}`;
|
|
632
|
+
}),
|
|
633
|
+
...findings.length > MAX_COMPLETION_FINDINGS ? [`${findings.length - MAX_COMPLETION_FINDINGS} more in the report; pending later feedback.`] : [],
|
|
634
|
+
`Evidence: ${reportPath}. Explicit audit: mason_automation(action: "check").`,
|
|
635
|
+
"Inspect findings within the authorized work. Concurrent changes may also contribute; this summary does not establish who introduced them."
|
|
636
|
+
].join("\n");
|
|
637
|
+
}
|
|
638
|
+
var conditionSchema, MAX_COMPLETION_FINDINGS, notificationStateSchema;
|
|
639
|
+
var init_notifications = __esm({
|
|
640
|
+
"src/automation/notifications.ts"() {
|
|
641
|
+
"use strict";
|
|
642
|
+
init_findings();
|
|
643
|
+
conditionSchema = z2.object({
|
|
644
|
+
level: z2.number(),
|
|
645
|
+
facts: z2.array(z2.string()),
|
|
646
|
+
magnitude: z2.number().optional()
|
|
647
|
+
});
|
|
648
|
+
MAX_COMPLETION_FINDINGS = 4;
|
|
649
|
+
notificationStateSchema = z2.object({
|
|
650
|
+
version: z2.literal(1),
|
|
651
|
+
baselineHead: z2.string(),
|
|
652
|
+
baseline: z2.record(conditionSchema),
|
|
653
|
+
delivered: z2.record(conditionSchema),
|
|
654
|
+
cleared: z2.array(z2.string())
|
|
655
|
+
});
|
|
656
|
+
}
|
|
657
|
+
});
|
|
658
|
+
|
|
659
|
+
// src/automation/store.ts
|
|
660
|
+
import { z as z3 } from "zod";
|
|
399
661
|
function parseState(raw) {
|
|
400
662
|
try {
|
|
401
663
|
return stateSchema.parse(raw);
|
|
@@ -411,36 +673,217 @@ var init_store = __esm({
|
|
|
411
673
|
"src/automation/store.ts"() {
|
|
412
674
|
"use strict";
|
|
413
675
|
init_store_lock();
|
|
414
|
-
|
|
676
|
+
init_notifications();
|
|
677
|
+
hostSchema = z3.enum(["claude", "codex"]);
|
|
415
678
|
events = ["session_start", "turn_start", "before_tool", "after_tool", "task_end"];
|
|
416
|
-
stateSchema =
|
|
417
|
-
version:
|
|
418
|
-
root:
|
|
419
|
-
gitDir:
|
|
420
|
-
branch:
|
|
421
|
-
baselines:
|
|
422
|
-
sessions:
|
|
679
|
+
stateSchema = z3.object({
|
|
680
|
+
version: z3.literal(1),
|
|
681
|
+
root: z3.string(),
|
|
682
|
+
gitDir: z3.string(),
|
|
683
|
+
branch: z3.string(),
|
|
684
|
+
baselines: z3.array(z3.object({ path: z3.string(), at: z3.string(), event: z3.string(), fingerprint: z3.string() })).max(128),
|
|
685
|
+
sessions: z3.record(z3.object({
|
|
423
686
|
host: hostSchema,
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
mutationObserved: z.boolean(),
|
|
430
|
-
pending: z.record(z.string()),
|
|
431
|
-
coverageGaps: z.array(z.string()),
|
|
432
|
-
events: z.record(z.object({ at: z.string(), count: z.number().int().positive() }))
|
|
687
|
+
lastUsed: z3.string(),
|
|
688
|
+
notifiedDiagnostics: z3.array(z3.string()).default([]),
|
|
689
|
+
pending: z3.record(z3.string()),
|
|
690
|
+
coverageGaps: z3.array(z3.string()),
|
|
691
|
+
events: z3.record(z3.object({ at: z3.string(), count: z3.number().int().positive() }))
|
|
433
692
|
})),
|
|
434
|
-
updatedAt:
|
|
435
|
-
fingerprint:
|
|
436
|
-
latest:
|
|
437
|
-
|
|
693
|
+
updatedAt: z3.string(),
|
|
694
|
+
fingerprint: z3.string().nullable(),
|
|
695
|
+
latest: z3.string().nullable(),
|
|
696
|
+
notifications: notificationStateSchema.optional(),
|
|
697
|
+
agentNotifications: notificationStateSchema.optional(),
|
|
698
|
+
analysis: z3.object({ id: z3.string(), fingerprint: z3.string(), completedAt: z3.number(), path: z3.string() }).optional()
|
|
438
699
|
});
|
|
439
700
|
}
|
|
440
701
|
});
|
|
441
702
|
|
|
442
|
-
// src/audit/
|
|
703
|
+
// src/audit/inspection.ts
|
|
704
|
+
import fg2 from "fast-glob";
|
|
705
|
+
import { AsyncLocalStorage as AsyncLocalStorage2 } from "async_hooks";
|
|
443
706
|
import path4 from "path";
|
|
707
|
+
function withRepositoryInspection(root, run) {
|
|
708
|
+
return inspections.run({ root: path4.resolve(root), reads: /* @__PURE__ */ new Map() }, run);
|
|
709
|
+
}
|
|
710
|
+
function inspectionRead(root, key, read) {
|
|
711
|
+
const inspection = inspections.getStore();
|
|
712
|
+
if (!inspection || inspection.root !== path4.resolve(root)) return read();
|
|
713
|
+
if (!inspection.reads.has(key)) {
|
|
714
|
+
const pending = read();
|
|
715
|
+
inspection.reads.set(key, pending);
|
|
716
|
+
void pending.catch(() => {
|
|
717
|
+
if (inspection.reads.get(key) === pending) inspection.reads.delete(key);
|
|
718
|
+
});
|
|
719
|
+
}
|
|
720
|
+
return inspection.reads.get(key);
|
|
721
|
+
}
|
|
722
|
+
function inspectionGit(args2, options) {
|
|
723
|
+
return inspectionRead(options.cwd, "git:" + JSON.stringify([args2, options]), () => execGit(args2, options));
|
|
724
|
+
}
|
|
725
|
+
async function auditGitPaths(root, kind) {
|
|
726
|
+
const custom = (await loadProjectConfig(root)).audit?.include ?? [];
|
|
727
|
+
const shared = inspections.getStore()?.root === path4.resolve(root) && !custom.length;
|
|
728
|
+
const expanded = fg2.generateTasks(custom).flatMap((task) => task.positive);
|
|
729
|
+
if (expanded.length > 1e3) throw new Error("Audit include patterns expand to more than 1,000 globs");
|
|
730
|
+
const documentSpecs = [...docSpecs, ...expanded.map((pattern) => `:(glob)${pattern}`)];
|
|
731
|
+
const specs = shared ? [...documentSpecs, ...sourceSpecs] : kind === "documents" ? documentSpecs : sourceSpecs;
|
|
732
|
+
const limit = kind === "documents" ? 16 * 1024 * 1024 : 32 * 1024 * 1024;
|
|
733
|
+
const { stdout } = await inspectionGit(
|
|
734
|
+
["ls-files", "-z", "--cached", "--others", "--exclude-standard", "--", ...specs],
|
|
735
|
+
{ cwd: root, maxBuffer: shared ? 48 * 1024 * 1024 : limit, timeout: 1e4 }
|
|
736
|
+
);
|
|
737
|
+
const paths = [...new Set(stdout.split("\0").filter(Boolean))].filter((file) => kind === "documents" ? custom.length > 0 || /^(readme|agents|claude)\.md$/i.test(path4.posix.basename(file)) : sourceSuffixes.has(file.slice(file.lastIndexOf("."))));
|
|
738
|
+
if (Buffer.byteLength(paths.join("\0")) > limit) throw new Error("Git " + kind + " inventory exceeds its bounded read limit.");
|
|
739
|
+
return paths.sort();
|
|
740
|
+
}
|
|
741
|
+
var inspections, sourceSpecs, docSpecs, sourceSuffixes;
|
|
742
|
+
var init_inspection = __esm({
|
|
743
|
+
"src/audit/inspection.ts"() {
|
|
744
|
+
"use strict";
|
|
745
|
+
init_git_read();
|
|
746
|
+
init_files();
|
|
747
|
+
inspections = new AsyncLocalStorage2();
|
|
748
|
+
sourceSpecs = SOURCE_EXTENSIONS.map((extension) => `:(glob)**/*.${extension}`);
|
|
749
|
+
docSpecs = [":(icase,glob)**/readme.md", ":(icase,glob)**/agents.md", ":(icase,glob)**/claude.md"];
|
|
750
|
+
sourceSuffixes = new Set(SOURCE_EXTENSIONS.map((extension) => "." + extension));
|
|
751
|
+
}
|
|
752
|
+
});
|
|
753
|
+
|
|
754
|
+
// src/audit/inputs.ts
|
|
755
|
+
import fs4 from "fs/promises";
|
|
756
|
+
import * as nativeFs from "fs";
|
|
757
|
+
import path5 from "path";
|
|
758
|
+
import fg3 from "fast-glob";
|
|
759
|
+
async function auditInputPath(root, file) {
|
|
760
|
+
if (file === ".") return fs4.realpath(root);
|
|
761
|
+
try {
|
|
762
|
+
return await storePath(root, file);
|
|
763
|
+
} catch (error) {
|
|
764
|
+
if (error instanceof Error && error.message.startsWith("Symlink")) {
|
|
765
|
+
throw new Error(`Audit input contains a symbolic link: ${file}. Its evidence could not be verified.`, { cause: error });
|
|
766
|
+
}
|
|
767
|
+
throw error;
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
async function readAuditInput(root, file) {
|
|
771
|
+
try {
|
|
772
|
+
const value = await readBoundedFile(await auditInputPath(root, file), 10 * 1024 * 1024);
|
|
773
|
+
if (value === null) throw new Error(`Unreadable or oversized audit input: ${file}`);
|
|
774
|
+
return value;
|
|
775
|
+
} catch (error) {
|
|
776
|
+
if (["ENOENT", "ENOTDIR"].includes(error.code ?? "")) return null;
|
|
777
|
+
throw error;
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
async function auditGlob(root, patterns, options) {
|
|
781
|
+
if (Array.isArray(patterns) && !patterns.length) return [];
|
|
782
|
+
const directories = /* @__PURE__ */ new Set();
|
|
783
|
+
const readdir2 = ((directory, opts, callback) => {
|
|
784
|
+
const relative = path5.relative(root, directory).split(path5.sep).join("/") || ".";
|
|
785
|
+
directories.add(relative);
|
|
786
|
+
if (directories.size > MAX_INPUTS) {
|
|
787
|
+
callback(new Error(`${options.label} exceeds ${MAX_INPUTS.toLocaleString("en-US")} directories while inspecting ${relative}. Narrow this check's inputs.`));
|
|
788
|
+
return;
|
|
789
|
+
}
|
|
790
|
+
auditInputPath(root, relative).then(() => {
|
|
791
|
+
nativeFs.readdir(directory, opts, callback);
|
|
792
|
+
}, callback);
|
|
793
|
+
});
|
|
794
|
+
const lstat2 = ((file, callback) => {
|
|
795
|
+
const relative = path5.relative(root, file).split(path5.sep).join("/") || ".";
|
|
796
|
+
auditInputPath(root, relative).then(() => nativeFs.lstat(file, callback), callback);
|
|
797
|
+
});
|
|
798
|
+
const stream = fg3.stream(patterns, {
|
|
799
|
+
cwd: root,
|
|
800
|
+
ignore: options.ignore,
|
|
801
|
+
onlyFiles: false,
|
|
802
|
+
onlyDirectories: options.onlyDirectories,
|
|
803
|
+
objectMode: true,
|
|
804
|
+
followSymbolicLinks: options.followSymbolicLinks ?? true,
|
|
805
|
+
fs: { readdir: readdir2, lstat: lstat2 },
|
|
806
|
+
concurrency: 16
|
|
807
|
+
});
|
|
808
|
+
const matches = [];
|
|
809
|
+
try {
|
|
810
|
+
for await (const value of stream) {
|
|
811
|
+
const entry = value;
|
|
812
|
+
const file = entry.path;
|
|
813
|
+
if (options.select && !options.select(file)) continue;
|
|
814
|
+
await auditInputPath(root, file);
|
|
815
|
+
if (!options.onlyDirectories && !entry.dirent.isFile()) continue;
|
|
816
|
+
matches.push(file);
|
|
817
|
+
if (matches.length > MAX_INPUTS) {
|
|
818
|
+
const largest = [...matches.reduce((counts, item) => {
|
|
819
|
+
const dir = item.split("/")[0];
|
|
820
|
+
counts.set(dir, (counts.get(dir) ?? 0) + 1);
|
|
821
|
+
return counts;
|
|
822
|
+
}, /* @__PURE__ */ new Map())].sort((a, b) => b[1] - a[1]).slice(0, 3);
|
|
823
|
+
throw new Error(`${options.label} exceeds ${MAX_INPUTS.toLocaleString("en-US")} relevant paths. Largest directories: ${largest.map(([dir, count4]) => `${dir} (${count4})`).join(", ")}. Narrow this check's inputs.`);
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
} finally {
|
|
827
|
+
stream.destroy();
|
|
828
|
+
}
|
|
829
|
+
return matches.sort();
|
|
830
|
+
}
|
|
831
|
+
async function gitSourcePaths(root) {
|
|
832
|
+
return auditGitPaths(root, "sources");
|
|
833
|
+
}
|
|
834
|
+
async function gitIgnoredPaths(root, files) {
|
|
835
|
+
if (!files.length) return /* @__PURE__ */ new Set();
|
|
836
|
+
const ignored = /* @__PURE__ */ new Set();
|
|
837
|
+
for (let offset = 0; offset < files.length; offset += 1e3) {
|
|
838
|
+
try {
|
|
839
|
+
const { stdout } = await execGit(["check-ignore", "-z", "--stdin"], {
|
|
840
|
+
cwd: root,
|
|
841
|
+
maxBuffer: 4 * 1024 * 1024,
|
|
842
|
+
timeout: 1e4,
|
|
843
|
+
input: files.slice(offset, offset + 1e3).join("\0") + "\0"
|
|
844
|
+
});
|
|
845
|
+
for (const file of stdout.split("\0").filter(Boolean)) ignored.add(file);
|
|
846
|
+
} catch (error) {
|
|
847
|
+
if (error.code !== 1) throw error;
|
|
848
|
+
}
|
|
849
|
+
}
|
|
850
|
+
return ignored;
|
|
851
|
+
}
|
|
852
|
+
var MAX_INPUTS;
|
|
853
|
+
var init_inputs = __esm({
|
|
854
|
+
"src/audit/inputs.ts"() {
|
|
855
|
+
"use strict";
|
|
856
|
+
init_files();
|
|
857
|
+
init_storage();
|
|
858
|
+
init_git_read();
|
|
859
|
+
init_inspection();
|
|
860
|
+
MAX_INPUTS = 1e5;
|
|
861
|
+
}
|
|
862
|
+
});
|
|
863
|
+
|
|
864
|
+
// src/audit/policy.ts
|
|
865
|
+
import fg4 from "fast-glob";
|
|
866
|
+
async function includedCheckPaths(root, paths, exclude) {
|
|
867
|
+
if (!exclude.length) return new Set(paths);
|
|
868
|
+
const included = /* @__PURE__ */ new Set();
|
|
869
|
+
for (let offset = 0; offset < paths.length; offset += 256) {
|
|
870
|
+
for (const file of await auditGlob(root, paths.slice(offset, offset + 256).map((file2) => fg4.escapePath(file2)), {
|
|
871
|
+
ignore: exclude,
|
|
872
|
+
followSymbolicLinks: false,
|
|
873
|
+
label: "Audit check exclusions"
|
|
874
|
+
})) included.add(file);
|
|
875
|
+
}
|
|
876
|
+
return included;
|
|
877
|
+
}
|
|
878
|
+
var init_policy = __esm({
|
|
879
|
+
"src/audit/policy.ts"() {
|
|
880
|
+
"use strict";
|
|
881
|
+
init_inputs();
|
|
882
|
+
}
|
|
883
|
+
});
|
|
884
|
+
|
|
885
|
+
// src/audit/commands.ts
|
|
886
|
+
import path6 from "path";
|
|
444
887
|
function splitCommands(line) {
|
|
445
888
|
const parts = [];
|
|
446
889
|
let quote2 = "", start = 0;
|
|
@@ -476,7 +919,7 @@ function commandClaims(line, lineNo, cwd, shell = false) {
|
|
|
476
919
|
const cd = text2.match(/^cd\s+(.+)$/);
|
|
477
920
|
if (cd) {
|
|
478
921
|
const directory = literal(cd[1]);
|
|
479
|
-
cwd = directory === null || cwd === null || /\s/.test(directory) && !/^["']/.test(cd[1]) ? null :
|
|
922
|
+
cwd = directory === null || cwd === null || /\s/.test(directory) && !/^["']/.test(cd[1]) ? null : path6.posix.join(cwd ?? ".", directory);
|
|
480
923
|
continue;
|
|
481
924
|
}
|
|
482
925
|
for (const match of text2.matchAll(COMMAND)) {
|
|
@@ -487,7 +930,7 @@ function commandClaims(line, lineNo, cwd, shell = false) {
|
|
|
487
930
|
if (shell && match.index !== 0 || match[3].startsWith("-")) scopeUnknown = "Unsupported shell wrapper or options before the script.";
|
|
488
931
|
if (selector) {
|
|
489
932
|
const selected = literal(selector[1]);
|
|
490
|
-
directory = selected === null || cwd === null ? null :
|
|
933
|
+
directory = selected === null || cwd === null ? null : path6.posix.join(cwd ?? ".", selected);
|
|
491
934
|
} else if (flags) scopeUnknown = "Unsupported package-manager options before run: " + flags;
|
|
492
935
|
const tail = text2.slice(match.index + match[0].length).split(/\s--(?:\s|$)/)[0];
|
|
493
936
|
if (/(?:^|\s)(?:--workspace(?:s)?|-w|--filter|-F|--prefix|--dir|-C|--if-present)(?:\s|=|$)/.test(tail)) {
|
|
@@ -517,7 +960,7 @@ var init_commands = __esm({
|
|
|
517
960
|
COMMAND = new RegExp(`\\b(npm|pnpm|yarn)((?:[ \\t]+${TOKEN})*?)[ \\t]+run[ \\t]+([A-Za-z0-9:_.-]+)`, "g");
|
|
518
961
|
literal = (value) => {
|
|
519
962
|
const unquoted = value.replace(/^(["'])(.*)\1$/, "$2");
|
|
520
|
-
return /[$`*?\[\]{}~\\\x00-\x1f]/.test(unquoted) ||
|
|
963
|
+
return /[$`*?\[\]{}~\\\x00-\x1f]/.test(unquoted) || path6.posix.isAbsolute(unquoted) ? null : unquoted;
|
|
521
964
|
};
|
|
522
965
|
}
|
|
523
966
|
});
|
|
@@ -822,52 +1265,6 @@ var init_claims = __esm({
|
|
|
822
1265
|
}
|
|
823
1266
|
});
|
|
824
1267
|
|
|
825
|
-
// src/audit/inspection.ts
|
|
826
|
-
import { AsyncLocalStorage as AsyncLocalStorage2 } from "async_hooks";
|
|
827
|
-
import path5 from "path";
|
|
828
|
-
function withRepositoryInspection(root, run) {
|
|
829
|
-
return inspections.run({ root: path5.resolve(root), reads: /* @__PURE__ */ new Map() }, run);
|
|
830
|
-
}
|
|
831
|
-
function inspectionRead(root, key, read) {
|
|
832
|
-
const inspection = inspections.getStore();
|
|
833
|
-
if (!inspection || inspection.root !== path5.resolve(root)) return read();
|
|
834
|
-
if (!inspection.reads.has(key)) {
|
|
835
|
-
const pending = read();
|
|
836
|
-
inspection.reads.set(key, pending);
|
|
837
|
-
void pending.catch(() => {
|
|
838
|
-
if (inspection.reads.get(key) === pending) inspection.reads.delete(key);
|
|
839
|
-
});
|
|
840
|
-
}
|
|
841
|
-
return inspection.reads.get(key);
|
|
842
|
-
}
|
|
843
|
-
function inspectionGit(args2, options) {
|
|
844
|
-
return inspectionRead(options.cwd, "git:" + JSON.stringify([args2, options]), () => execGit(args2, options));
|
|
845
|
-
}
|
|
846
|
-
async function auditGitPaths(root, kind) {
|
|
847
|
-
const shared = inspections.getStore()?.root === path5.resolve(root);
|
|
848
|
-
const specs = shared ? [...docSpecs, ...sourceSpecs] : kind === "documents" ? docSpecs : sourceSpecs;
|
|
849
|
-
const limit = kind === "documents" ? 16 * 1024 * 1024 : 32 * 1024 * 1024;
|
|
850
|
-
const { stdout } = await inspectionGit(
|
|
851
|
-
["ls-files", "-z", "--cached", "--others", "--exclude-standard", "--", ...specs],
|
|
852
|
-
{ cwd: root, maxBuffer: shared ? 48 * 1024 * 1024 : limit, timeout: 1e4 }
|
|
853
|
-
);
|
|
854
|
-
const paths = [...new Set(stdout.split("\0").filter(Boolean))].filter((file) => kind === "documents" ? /^(readme|agents|claude)\.md$/i.test(path5.posix.basename(file)) : sourceSuffixes.has(file.slice(file.lastIndexOf("."))));
|
|
855
|
-
if (Buffer.byteLength(paths.join("\0")) > limit) throw new Error("Git " + kind + " inventory exceeds its bounded read limit.");
|
|
856
|
-
return paths.sort();
|
|
857
|
-
}
|
|
858
|
-
var inspections, sourceSpecs, docSpecs, sourceSuffixes;
|
|
859
|
-
var init_inspection = __esm({
|
|
860
|
-
"src/audit/inspection.ts"() {
|
|
861
|
-
"use strict";
|
|
862
|
-
init_git_read();
|
|
863
|
-
init_files();
|
|
864
|
-
inspections = new AsyncLocalStorage2();
|
|
865
|
-
sourceSpecs = SOURCE_EXTENSIONS.map((extension) => `:(glob)**/*.${extension}`);
|
|
866
|
-
docSpecs = [":(icase,glob)**/readme.md", ":(icase,glob)**/agents.md", ":(icase,glob)**/claude.md"];
|
|
867
|
-
sourceSuffixes = new Set(SOURCE_EXTENSIONS.map((extension) => "." + extension));
|
|
868
|
-
}
|
|
869
|
-
});
|
|
870
|
-
|
|
871
1268
|
// src/audit/git.ts
|
|
872
1269
|
function parseCommitLine(line) {
|
|
873
1270
|
const parts = line.split(" ");
|
|
@@ -946,127 +1343,17 @@ async function commitsTouchingSince(resolvedRoot, fromHash, pathspecs) {
|
|
|
946
1343
|
}
|
|
947
1344
|
var COMMIT_FORMAT;
|
|
948
1345
|
var init_git = __esm({
|
|
949
|
-
"src/audit/git.ts"() {
|
|
950
|
-
"use strict";
|
|
951
|
-
init_inspection();
|
|
952
|
-
COMMIT_FORMAT = "%H%x09%cI%x09%s";
|
|
953
|
-
}
|
|
954
|
-
});
|
|
955
|
-
|
|
956
|
-
// src/audit/inputs.ts
|
|
957
|
-
import fs4 from "fs/promises";
|
|
958
|
-
import * as nativeFs from "fs";
|
|
959
|
-
import path6 from "path";
|
|
960
|
-
import fg2 from "fast-glob";
|
|
961
|
-
async function auditInputPath(root, file) {
|
|
962
|
-
if (file === ".") return fs4.realpath(root);
|
|
963
|
-
try {
|
|
964
|
-
return await storePath(root, file);
|
|
965
|
-
} catch (error) {
|
|
966
|
-
if (error instanceof Error && error.message.startsWith("Symlink")) {
|
|
967
|
-
throw new Error(`Audit input contains a symbolic link: ${file}. Its evidence could not be verified.`, { cause: error });
|
|
968
|
-
}
|
|
969
|
-
throw error;
|
|
970
|
-
}
|
|
971
|
-
}
|
|
972
|
-
async function readAuditInput(root, file) {
|
|
973
|
-
try {
|
|
974
|
-
const value = await readBoundedFile(await auditInputPath(root, file), 10 * 1024 * 1024);
|
|
975
|
-
if (value === null) throw new Error(`Unreadable or oversized audit input: ${file}`);
|
|
976
|
-
return value;
|
|
977
|
-
} catch (error) {
|
|
978
|
-
if (["ENOENT", "ENOTDIR"].includes(error.code ?? "")) return null;
|
|
979
|
-
throw error;
|
|
980
|
-
}
|
|
981
|
-
}
|
|
982
|
-
async function auditGlob(root, patterns, options) {
|
|
983
|
-
if (Array.isArray(patterns) && !patterns.length) return [];
|
|
984
|
-
const directories = /* @__PURE__ */ new Set();
|
|
985
|
-
const readdir2 = ((directory, opts, callback) => {
|
|
986
|
-
const relative = path6.relative(root, directory).split(path6.sep).join("/") || ".";
|
|
987
|
-
directories.add(relative);
|
|
988
|
-
if (directories.size > MAX_INPUTS) {
|
|
989
|
-
callback(new Error(`${options.label} exceeds ${MAX_INPUTS.toLocaleString("en-US")} directories while inspecting ${relative}. Narrow this check's inputs.`));
|
|
990
|
-
return;
|
|
991
|
-
}
|
|
992
|
-
auditInputPath(root, relative).then(() => {
|
|
993
|
-
nativeFs.readdir(directory, opts, callback);
|
|
994
|
-
}, callback);
|
|
995
|
-
});
|
|
996
|
-
const lstat2 = ((file, callback) => {
|
|
997
|
-
const relative = path6.relative(root, file).split(path6.sep).join("/") || ".";
|
|
998
|
-
auditInputPath(root, relative).then(() => nativeFs.lstat(file, callback), callback);
|
|
999
|
-
});
|
|
1000
|
-
const stream = fg2.stream(patterns, {
|
|
1001
|
-
cwd: root,
|
|
1002
|
-
ignore: options.ignore,
|
|
1003
|
-
onlyFiles: false,
|
|
1004
|
-
onlyDirectories: options.onlyDirectories,
|
|
1005
|
-
objectMode: true,
|
|
1006
|
-
followSymbolicLinks: options.followSymbolicLinks ?? true,
|
|
1007
|
-
fs: { readdir: readdir2, lstat: lstat2 },
|
|
1008
|
-
concurrency: 16
|
|
1009
|
-
});
|
|
1010
|
-
const matches = [];
|
|
1011
|
-
try {
|
|
1012
|
-
for await (const value of stream) {
|
|
1013
|
-
const entry = value;
|
|
1014
|
-
const file = entry.path;
|
|
1015
|
-
if (options.select && !options.select(file)) continue;
|
|
1016
|
-
await auditInputPath(root, file);
|
|
1017
|
-
if (!options.onlyDirectories && !entry.dirent.isFile()) continue;
|
|
1018
|
-
matches.push(file);
|
|
1019
|
-
if (matches.length > MAX_INPUTS) {
|
|
1020
|
-
const largest = [...matches.reduce((counts, item) => {
|
|
1021
|
-
const dir = item.split("/")[0];
|
|
1022
|
-
counts.set(dir, (counts.get(dir) ?? 0) + 1);
|
|
1023
|
-
return counts;
|
|
1024
|
-
}, /* @__PURE__ */ new Map())].sort((a, b) => b[1] - a[1]).slice(0, 3);
|
|
1025
|
-
throw new Error(`${options.label} exceeds ${MAX_INPUTS.toLocaleString("en-US")} relevant paths. Largest directories: ${largest.map(([dir, count4]) => `${dir} (${count4})`).join(", ")}. Narrow this check's inputs.`);
|
|
1026
|
-
}
|
|
1027
|
-
}
|
|
1028
|
-
} finally {
|
|
1029
|
-
stream.destroy();
|
|
1030
|
-
}
|
|
1031
|
-
return matches.sort();
|
|
1032
|
-
}
|
|
1033
|
-
async function gitSourcePaths(root) {
|
|
1034
|
-
return auditGitPaths(root, "sources");
|
|
1035
|
-
}
|
|
1036
|
-
async function gitIgnoredPaths(root, files) {
|
|
1037
|
-
if (!files.length) return /* @__PURE__ */ new Set();
|
|
1038
|
-
const ignored = /* @__PURE__ */ new Set();
|
|
1039
|
-
for (let offset = 0; offset < files.length; offset += 1e3) {
|
|
1040
|
-
try {
|
|
1041
|
-
const { stdout } = await execGit(["check-ignore", "-z", "--stdin"], {
|
|
1042
|
-
cwd: root,
|
|
1043
|
-
maxBuffer: 4 * 1024 * 1024,
|
|
1044
|
-
timeout: 1e4,
|
|
1045
|
-
input: files.slice(offset, offset + 1e3).join("\0") + "\0"
|
|
1046
|
-
});
|
|
1047
|
-
for (const file of stdout.split("\0").filter(Boolean)) ignored.add(file);
|
|
1048
|
-
} catch (error) {
|
|
1049
|
-
if (error.code !== 1) throw error;
|
|
1050
|
-
}
|
|
1051
|
-
}
|
|
1052
|
-
return ignored;
|
|
1053
|
-
}
|
|
1054
|
-
var MAX_INPUTS;
|
|
1055
|
-
var init_inputs = __esm({
|
|
1056
|
-
"src/audit/inputs.ts"() {
|
|
1057
|
-
"use strict";
|
|
1058
|
-
init_files();
|
|
1059
|
-
init_storage();
|
|
1060
|
-
init_git_read();
|
|
1346
|
+
"src/audit/git.ts"() {
|
|
1347
|
+
"use strict";
|
|
1061
1348
|
init_inspection();
|
|
1062
|
-
|
|
1349
|
+
COMMIT_FORMAT = "%H%x09%cI%x09%s";
|
|
1063
1350
|
}
|
|
1064
1351
|
});
|
|
1065
1352
|
|
|
1066
1353
|
// src/audit/docs.ts
|
|
1067
1354
|
import fs5 from "fs/promises";
|
|
1068
1355
|
import path7 from "path";
|
|
1069
|
-
import
|
|
1356
|
+
import fg5 from "fast-glob";
|
|
1070
1357
|
async function dirtyDocs(resolvedRoot, files) {
|
|
1071
1358
|
if (!files.length) return /* @__PURE__ */ new Set();
|
|
1072
1359
|
try {
|
|
@@ -1118,7 +1405,7 @@ async function discoverDocPaths(root) {
|
|
|
1118
1405
|
const paths = [];
|
|
1119
1406
|
const unique = [...new Set(candidates)].sort();
|
|
1120
1407
|
for (let offset = 0; offset < unique.length; offset += 256) {
|
|
1121
|
-
paths.push(...await auditGlob(root, unique.slice(offset, offset + 256).map((p) =>
|
|
1408
|
+
paths.push(...await auditGlob(root, unique.slice(offset, offset + 256).map((p) => fg5.escapePath(p)), {
|
|
1122
1409
|
ignore: excluded,
|
|
1123
1410
|
followSymbolicLinks: false,
|
|
1124
1411
|
label: "Documentation discovery"
|
|
@@ -1148,6 +1435,7 @@ async function localDocPaths(root) {
|
|
|
1148
1435
|
return paths;
|
|
1149
1436
|
}
|
|
1150
1437
|
function documentScope(file) {
|
|
1438
|
+
if (!/^(readme|agents|claude)\.md$/i.test(path7.posix.basename(file))) return ".";
|
|
1151
1439
|
const parent = path7.posix.dirname(file);
|
|
1152
1440
|
return path7.posix.basename(parent).toLowerCase() === ".claude" ? path7.posix.dirname(parent) : parent;
|
|
1153
1441
|
}
|
|
@@ -1169,7 +1457,7 @@ async function readDocs(resolvedRoot) {
|
|
|
1169
1457
|
path: candidate,
|
|
1170
1458
|
content: content2,
|
|
1171
1459
|
scope: documentScope(candidate),
|
|
1172
|
-
kind: /^readme\.md$/i.test(path7.posix.basename(candidate)) ? "readme" : "instructions",
|
|
1460
|
+
kind: /^readme\.md$/i.test(path7.posix.basename(candidate)) ? "readme" : /^(agents|claude)\.md$/i.test(path7.posix.basename(candidate)) ? "instructions" : "guide",
|
|
1173
1461
|
lineCount: content2.split("\n").length,
|
|
1174
1462
|
lastCommit,
|
|
1175
1463
|
dirty: dirty.has(candidate),
|
|
@@ -1326,7 +1614,7 @@ var init_test_map = __esm({
|
|
|
1326
1614
|
|
|
1327
1615
|
// src/snapshot/snapshot.ts
|
|
1328
1616
|
import path10 from "path";
|
|
1329
|
-
import { z as
|
|
1617
|
+
import { z as z4 } from "zod";
|
|
1330
1618
|
async function inspectSnapshot(rootDir) {
|
|
1331
1619
|
try {
|
|
1332
1620
|
const raw = await readStoreJson(rootDir, ".mason/snapshot.json");
|
|
@@ -1360,30 +1648,30 @@ var init_snapshot = __esm({
|
|
|
1360
1648
|
init_lock();
|
|
1361
1649
|
init_paths();
|
|
1362
1650
|
init_test_map();
|
|
1363
|
-
repoPath =
|
|
1651
|
+
repoPath = z4.string().refine((value) => normalizeRepoPath(value) !== null, "Expected a relative repository path");
|
|
1364
1652
|
verificationFields = {
|
|
1365
|
-
refreshedHash:
|
|
1366
|
-
verifiedAt:
|
|
1367
|
-
verifiedHash:
|
|
1368
|
-
verificationToken:
|
|
1369
|
-
verificationFailed:
|
|
1370
|
-
verificationNote:
|
|
1653
|
+
refreshedHash: z4.string().optional(),
|
|
1654
|
+
verifiedAt: z4.string().optional(),
|
|
1655
|
+
verifiedHash: z4.string().optional(),
|
|
1656
|
+
verificationToken: z4.string().optional(),
|
|
1657
|
+
verificationFailed: z4.boolean().optional(),
|
|
1658
|
+
verificationNote: z4.string().optional()
|
|
1371
1659
|
};
|
|
1372
|
-
featureSchema =
|
|
1373
|
-
description:
|
|
1374
|
-
files:
|
|
1375
|
-
tests:
|
|
1376
|
-
type:
|
|
1660
|
+
featureSchema = z4.object({
|
|
1661
|
+
description: z4.string(),
|
|
1662
|
+
files: z4.array(repoPath),
|
|
1663
|
+
tests: z4.array(repoPath).optional(),
|
|
1664
|
+
type: z4.enum(["capability", "infrastructure"]).optional(),
|
|
1377
1665
|
...verificationFields
|
|
1378
1666
|
}).passthrough();
|
|
1379
|
-
flowSchema =
|
|
1380
|
-
snapshotSchema =
|
|
1381
|
-
version:
|
|
1382
|
-
createdAt:
|
|
1383
|
-
updatedAt:
|
|
1384
|
-
gitHash:
|
|
1385
|
-
features:
|
|
1386
|
-
flows:
|
|
1667
|
+
flowSchema = z4.object({ description: z4.string(), chain: z4.array(repoPath), ...verificationFields }).passthrough();
|
|
1668
|
+
snapshotSchema = z4.object({
|
|
1669
|
+
version: z4.literal(2),
|
|
1670
|
+
createdAt: z4.string(),
|
|
1671
|
+
updatedAt: z4.string(),
|
|
1672
|
+
gitHash: z4.string(),
|
|
1673
|
+
features: z4.record(featureSchema),
|
|
1674
|
+
flows: z4.record(flowSchema)
|
|
1387
1675
|
}).passthrough();
|
|
1388
1676
|
}
|
|
1389
1677
|
});
|
|
@@ -1438,22 +1726,6 @@ var init_drift = __esm({
|
|
|
1438
1726
|
}
|
|
1439
1727
|
});
|
|
1440
1728
|
|
|
1441
|
-
// src/audit/types.ts
|
|
1442
|
-
var ALL_CHECKS;
|
|
1443
|
-
var init_types = __esm({
|
|
1444
|
-
"src/audit/types.ts"() {
|
|
1445
|
-
"use strict";
|
|
1446
|
-
ALL_CHECKS = [
|
|
1447
|
-
"deleted-reference",
|
|
1448
|
-
"new-module",
|
|
1449
|
-
"stale-count",
|
|
1450
|
-
"dead-command",
|
|
1451
|
-
"deps-changed",
|
|
1452
|
-
"decision-anchor-drift"
|
|
1453
|
-
];
|
|
1454
|
-
}
|
|
1455
|
-
});
|
|
1456
|
-
|
|
1457
1729
|
// src/audit/checks/deleted-reference.ts
|
|
1458
1730
|
import path12 from "path";
|
|
1459
1731
|
async function checkDeletedReferences(ctx) {
|
|
@@ -1522,7 +1794,7 @@ var init_deleted_reference = __esm({
|
|
|
1522
1794
|
});
|
|
1523
1795
|
|
|
1524
1796
|
// src/audit/checks/new-module.ts
|
|
1525
|
-
import
|
|
1797
|
+
import fg6 from "fast-glob";
|
|
1526
1798
|
import fs8 from "fs/promises";
|
|
1527
1799
|
import path13 from "path";
|
|
1528
1800
|
function escapeRegExp(text2) {
|
|
@@ -1584,7 +1856,7 @@ async function collectModuleCandidates(root, combinedDocs) {
|
|
|
1584
1856
|
const result = /* @__PURE__ */ new Map();
|
|
1585
1857
|
for (const dir of parents) {
|
|
1586
1858
|
const visible = new Set(byParent.get(dir).filter((candidate) => !ignored.has(candidate) || sources.some((file) => file.startsWith(candidate + "/"))));
|
|
1587
|
-
result.set(dir, (await auditGlob(root, dir === "." ? "*" : `${
|
|
1859
|
+
result.set(dir, (await auditGlob(root, dir === "." ? "*" : `${fg6.escapePath(dir)}/*`, {
|
|
1588
1860
|
ignore,
|
|
1589
1861
|
onlyDirectories: true,
|
|
1590
1862
|
label: "Module directory discovery",
|
|
@@ -1597,7 +1869,7 @@ async function collectModuleCandidates(root, combinedDocs) {
|
|
|
1597
1869
|
const files = sources.filter((file) => file.startsWith(dir + "/"));
|
|
1598
1870
|
let count4 = 0;
|
|
1599
1871
|
for (let offset = 0; offset < files.length; offset += 256) {
|
|
1600
|
-
count4 += (await auditGlob(root, files.slice(offset, offset + 256).map((file) =>
|
|
1872
|
+
count4 += (await auditGlob(root, files.slice(offset, offset + 256).map((file) => fg6.escapePath(file)), {
|
|
1601
1873
|
ignore,
|
|
1602
1874
|
followSymbolicLinks: false,
|
|
1603
1875
|
label: `Source discovery in ${dir}`
|
|
@@ -1932,6 +2204,87 @@ var init_dead_command = __esm({
|
|
|
1932
2204
|
}
|
|
1933
2205
|
});
|
|
1934
2206
|
|
|
2207
|
+
// src/audit/dependency-claims.ts
|
|
2208
|
+
import path16 from "path";
|
|
2209
|
+
import { parse as parseToml } from "smol-toml";
|
|
2210
|
+
function dependencyText(content2) {
|
|
2211
|
+
return content2.replace(/<!-- mason:start -->[\s\S]*?<!-- mason:end -->/g, (text2) => text2.replace(/[^\n]/g, " ")).replace(/<!--[\s\S]*?-->/g, (text2) => text2.replace(/[^\n]/g, " "));
|
|
2212
|
+
}
|
|
2213
|
+
function declarations(file, source) {
|
|
2214
|
+
const result = /* @__PURE__ */ new Map();
|
|
2215
|
+
if (!source) return result;
|
|
2216
|
+
if (path16.posix.basename(file) === "package.json") {
|
|
2217
|
+
const json2 = JSON.parse(source);
|
|
2218
|
+
for (const section of ["dependencies", "devDependencies", "peerDependencies", "optionalDependencies", "engines"]) {
|
|
2219
|
+
for (const [name, value] of Object.entries(json2[section] ?? {})) {
|
|
2220
|
+
if (typeof value === "string") result.set(name, [...result.has(name) ? [result.get(name)] : [], value].join("; "));
|
|
2221
|
+
}
|
|
2222
|
+
}
|
|
2223
|
+
} else if (file.endsWith("libs.versions.toml")) {
|
|
2224
|
+
const catalog = parseToml(source);
|
|
2225
|
+
for (const section of ["libraries", "plugins"]) for (const [alias, entry] of Object.entries(catalog[section] ?? {})) {
|
|
2226
|
+
if (typeof entry === "string") {
|
|
2227
|
+
const parts = entry.split(":");
|
|
2228
|
+
if (parts.length >= 3) result.set(parts.slice(0, -1).join(":"), parts.at(-1));
|
|
2229
|
+
} else if (entry && typeof entry === "object") {
|
|
2230
|
+
const name = entry.module ?? (entry.group && entry.name ? `${entry.group}:${entry.name}` : entry.id);
|
|
2231
|
+
const version = typeof entry.version === "string" ? entry.version : catalog.versions?.[entry.version?.ref];
|
|
2232
|
+
if (typeof name === "string") result.set(name, typeof version === "string" ? version : "unspecified");
|
|
2233
|
+
}
|
|
2234
|
+
}
|
|
2235
|
+
} else if (/build\.gradle(?:\.kts)?$/.test(file)) {
|
|
2236
|
+
for (const match of source.matchAll(/["']([\w.-]+:[\w.-]+):([^"'\s]+)["']/g)) result.set(match[1], match[2]);
|
|
2237
|
+
for (const match of source.matchAll(/\bid\s*\(?\s*["']([\w.-]+)["']\s*\)?\s*version\s*["']([^"']+)["']/g)) result.set(match[1], match[2]);
|
|
2238
|
+
}
|
|
2239
|
+
return result;
|
|
2240
|
+
}
|
|
2241
|
+
async function matchDependencyChanges(root, from, head, files, content2) {
|
|
2242
|
+
const matches = [];
|
|
2243
|
+
const lines = dependencyText(content2).split("\n");
|
|
2244
|
+
let incomplete = files.length > 64;
|
|
2245
|
+
for (const file of files.slice(0, 64)) {
|
|
2246
|
+
if (!/(?:^|\/)(?:package\.json|libs\.versions\.toml|build\.gradle(?:\.kts)?)$/.test(file)) {
|
|
2247
|
+
incomplete = true;
|
|
2248
|
+
continue;
|
|
2249
|
+
}
|
|
2250
|
+
try {
|
|
2251
|
+
const read = async (revision) => {
|
|
2252
|
+
const { stdout: entry } = await inspectionGit(["ls-tree", revision, "--", `:(literal)${file}`], { cwd: root, timeout: 1e4 });
|
|
2253
|
+
if (!entry) return "";
|
|
2254
|
+
if (!entry.startsWith("100644 ") && !entry.startsWith("100755 ")) throw new Error("Unsupported manifest mode");
|
|
2255
|
+
return (await inspectionGit(["show", `${revision}:${file}`], { cwd: root, maxBuffer: 1024 * 1024, timeout: 1e4 })).stdout;
|
|
2256
|
+
};
|
|
2257
|
+
const [before, after] = await Promise.all([read(from), read(head)]);
|
|
2258
|
+
const old = declarations(file, before), next = declarations(file, after);
|
|
2259
|
+
for (const dependency of /* @__PURE__ */ new Set([...old.keys(), ...next.keys()])) {
|
|
2260
|
+
if (old.get(dependency) === next.get(dependency)) continue;
|
|
2261
|
+
const escaped = dependency.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
2262
|
+
const mention = new RegExp(`(^|[^a-zA-Z0-9_@./-])${escaped}(?![a-zA-Z0-9_/-]|\\.[a-zA-Z0-9_])`, "i");
|
|
2263
|
+
const line = lines.findIndex((text2) => mention.test(text2));
|
|
2264
|
+
if (line < 0) continue;
|
|
2265
|
+
matches.push({
|
|
2266
|
+
dependency,
|
|
2267
|
+
manifest: file,
|
|
2268
|
+
before: old.get(dependency) ?? null,
|
|
2269
|
+
after: next.get(dependency) ?? null,
|
|
2270
|
+
line: line + 1,
|
|
2271
|
+
excerpt: lines[line].trim().slice(0, 500)
|
|
2272
|
+
});
|
|
2273
|
+
if (matches.length >= 100) return { matches, incomplete: true };
|
|
2274
|
+
}
|
|
2275
|
+
} catch {
|
|
2276
|
+
incomplete = true;
|
|
2277
|
+
}
|
|
2278
|
+
}
|
|
2279
|
+
return { matches, incomplete };
|
|
2280
|
+
}
|
|
2281
|
+
var init_dependency_claims = __esm({
|
|
2282
|
+
"src/audit/dependency-claims.ts"() {
|
|
2283
|
+
"use strict";
|
|
2284
|
+
init_inspection();
|
|
2285
|
+
}
|
|
2286
|
+
});
|
|
2287
|
+
|
|
1935
2288
|
// src/audit/release-metadata.ts
|
|
1936
2289
|
async function git(root, args2) {
|
|
1937
2290
|
return (await execGit(args2, { cwd: root, timeout: 1e4, maxBuffer: 2 * 1024 * 1024 })).stdout;
|
|
@@ -2011,13 +2364,6 @@ async function checkDepsChanged(ctx) {
|
|
|
2011
2364
|
});
|
|
2012
2365
|
continue;
|
|
2013
2366
|
}
|
|
2014
|
-
if (doc.dirty) {
|
|
2015
|
-
result.skipped.push({
|
|
2016
|
-
check: "deps-changed",
|
|
2017
|
-
doc: doc.path,
|
|
2018
|
-
reason: `${doc.path} has uncommitted edits \u2013 suppressed while in flight`
|
|
2019
|
-
});
|
|
2020
|
-
}
|
|
2021
2367
|
let content2 = doc.content;
|
|
2022
2368
|
if (doc.dirty) {
|
|
2023
2369
|
try {
|
|
@@ -2030,7 +2376,6 @@ async function checkDepsChanged(ctx) {
|
|
|
2030
2376
|
continue;
|
|
2031
2377
|
}
|
|
2032
2378
|
}
|
|
2033
|
-
if (!hasDependencyContent(content2)) continue;
|
|
2034
2379
|
const range = await commitsTouchingSince(
|
|
2035
2380
|
ctx.root,
|
|
2036
2381
|
doc.lastCommit.hash,
|
|
@@ -2054,16 +2399,32 @@ async function checkDepsChanged(ctx) {
|
|
|
2054
2399
|
range.commits = relevant;
|
|
2055
2400
|
range.total = relevant.length;
|
|
2056
2401
|
if (range.total === 0) continue;
|
|
2057
|
-
const
|
|
2402
|
+
const relevance = await matchDependencyChanges(
|
|
2403
|
+
ctx.root,
|
|
2404
|
+
doc.lastCommit.hash,
|
|
2405
|
+
ctx.headHash,
|
|
2406
|
+
[...new Set(range.commits.flatMap((commit) => commit.files))],
|
|
2407
|
+
content2
|
|
2408
|
+
);
|
|
2409
|
+
if (!relevance.matches.length && !hasDependencyContent(content2)) continue;
|
|
2410
|
+
const match = relevance.matches[0];
|
|
2411
|
+
if (doc.dirty && match) result.skipped.push({
|
|
2412
|
+
check: "deps-changed",
|
|
2413
|
+
doc: doc.path,
|
|
2414
|
+
reason: `${doc.path} has uncommitted edits \u2013 suppressed while in flight`
|
|
2415
|
+
});
|
|
2058
2416
|
(doc.dirty ? result.suppressedAdvisories : result.advisories).push({
|
|
2059
2417
|
type: "deps-changed",
|
|
2060
|
-
|
|
2061
|
-
|
|
2418
|
+
...match ? {} : { resolution: "informational" },
|
|
2419
|
+
message: match ? `${match.dependency} changed from ${match.before ?? "absent"} to ${match.after ?? "removed"} in ${match.manifest}; review ${doc.path}:${match.line}: ${match.excerpt}${relevance.matches.length > 1 ? ` (${relevance.matches.length - 1} more matches in evidence)` : ""}` : `Dependency manifests changed after the document baseline ${doc.lastCommit.hash.slice(0, 7)} (${doc.lastCommit.date.slice(0, 10)}); no specific affected passage was established. Informational only.${relevance.incomplete ? " Some manifest declarations could not be matched." : ""}`,
|
|
2420
|
+
anchor: { doc: doc.path, line: match?.line ?? null, excerpt: match?.excerpt ?? null },
|
|
2062
2421
|
evidence: {
|
|
2063
2422
|
kind: "doc-behind-manifests",
|
|
2064
2423
|
docLastCommit: doc.lastCommit,
|
|
2065
2424
|
manifestCommits: range.commits.slice(0, MANIFEST_COMMITS_CAP),
|
|
2066
|
-
totalCommits: range.total
|
|
2425
|
+
totalCommits: range.total,
|
|
2426
|
+
matches: relevance.matches,
|
|
2427
|
+
matchingIncomplete: relevance.incomplete
|
|
2067
2428
|
}
|
|
2068
2429
|
});
|
|
2069
2430
|
}
|
|
@@ -2073,6 +2434,7 @@ var MANIFEST_COMMITS_CAP, MANIFEST_PATHSPECS;
|
|
|
2073
2434
|
var init_deps_changed = __esm({
|
|
2074
2435
|
"src/audit/checks/deps-changed.ts"() {
|
|
2075
2436
|
"use strict";
|
|
2437
|
+
init_dependency_claims();
|
|
2076
2438
|
init_docs();
|
|
2077
2439
|
init_release_metadata();
|
|
2078
2440
|
init_git();
|
|
@@ -2097,7 +2459,7 @@ var init_deps_changed = __esm({
|
|
|
2097
2459
|
});
|
|
2098
2460
|
|
|
2099
2461
|
// src/context/lexical.ts
|
|
2100
|
-
import
|
|
2462
|
+
import path17 from "path";
|
|
2101
2463
|
var init_lexical = __esm({
|
|
2102
2464
|
"src/context/lexical.ts"() {
|
|
2103
2465
|
"use strict";
|
|
@@ -2121,7 +2483,7 @@ var init_trust = __esm({
|
|
|
2121
2483
|
});
|
|
2122
2484
|
|
|
2123
2485
|
// src/decisions/provenance.ts
|
|
2124
|
-
import { z as
|
|
2486
|
+
import { z as z5 } from "zod";
|
|
2125
2487
|
function decisionContent(record) {
|
|
2126
2488
|
return {
|
|
2127
2489
|
title: record.title,
|
|
@@ -2185,67 +2547,67 @@ var init_provenance = __esm({
|
|
|
2185
2547
|
"use strict";
|
|
2186
2548
|
init_paths();
|
|
2187
2549
|
init_trust();
|
|
2188
|
-
text = (max) =>
|
|
2189
|
-
decisionSourceSchema =
|
|
2190
|
-
kind:
|
|
2550
|
+
text = (max) => z5.string().trim().min(1).max(max);
|
|
2551
|
+
decisionSourceSchema = z5.object({
|
|
2552
|
+
kind: z5.enum(["pull_request", "issue", "incident", "discussion", "document", "other"]),
|
|
2191
2553
|
reference: text(1e3),
|
|
2192
2554
|
note: text(500).optional()
|
|
2193
2555
|
}).strict();
|
|
2194
|
-
attributionSchema =
|
|
2556
|
+
attributionSchema = z5.object({
|
|
2195
2557
|
owner: text(200).nullable().optional(),
|
|
2196
|
-
sources:
|
|
2558
|
+
sources: z5.array(decisionSourceSchema).max(20).optional(),
|
|
2197
2559
|
actor: text(200).optional()
|
|
2198
2560
|
});
|
|
2199
|
-
contentSchema =
|
|
2200
|
-
title:
|
|
2201
|
-
body:
|
|
2202
|
-
category:
|
|
2203
|
-
files:
|
|
2561
|
+
contentSchema = z5.object({
|
|
2562
|
+
title: z5.string().min(1),
|
|
2563
|
+
body: z5.string().min(1),
|
|
2564
|
+
category: z5.enum(["decision", "gotcha", "deprecation", "convention"]),
|
|
2565
|
+
files: z5.array(z5.string().refine((f) => normalizeRepoPath(f) !== null)),
|
|
2204
2566
|
owner: text(200).optional(),
|
|
2205
|
-
sources:
|
|
2567
|
+
sources: z5.array(decisionSourceSchema).max(20)
|
|
2206
2568
|
});
|
|
2207
|
-
approvalSchema =
|
|
2208
|
-
statusSchema =
|
|
2209
|
-
reviewEvidenceSchema =
|
|
2210
|
-
baseHash:
|
|
2211
|
-
headHash:
|
|
2212
|
-
historyAvailable:
|
|
2213
|
-
changedFiles:
|
|
2214
|
-
localChanges:
|
|
2569
|
+
approvalSchema = z5.enum(["unreviewed", "proposed", "accepted"]);
|
|
2570
|
+
statusSchema = z5.enum(["active", "superseded", "retired"]);
|
|
2571
|
+
reviewEvidenceSchema = z5.object({
|
|
2572
|
+
baseHash: z5.string(),
|
|
2573
|
+
headHash: z5.string(),
|
|
2574
|
+
historyAvailable: z5.boolean(),
|
|
2575
|
+
changedFiles: z5.array(z5.string()),
|
|
2576
|
+
localChanges: z5.array(z5.string())
|
|
2215
2577
|
});
|
|
2216
|
-
eventSchema =
|
|
2217
|
-
kind:
|
|
2218
|
-
at:
|
|
2578
|
+
eventSchema = z5.object({
|
|
2579
|
+
kind: z5.enum(["imported", "created", "revised", "accepted", "reaffirmed", "retired", "superseded"]),
|
|
2580
|
+
at: z5.string().datetime(),
|
|
2219
2581
|
actor: text(200).optional(),
|
|
2220
2582
|
note: text(1500).optional(),
|
|
2221
|
-
revision:
|
|
2583
|
+
revision: z5.number().int().positive(),
|
|
2222
2584
|
content: contentSchema,
|
|
2223
2585
|
approval: approvalSchema,
|
|
2224
2586
|
status: statusSchema,
|
|
2225
|
-
refreshedHash:
|
|
2587
|
+
refreshedHash: z5.string(),
|
|
2226
2588
|
evidence: reviewEvidenceSchema.optional()
|
|
2227
2589
|
});
|
|
2228
|
-
legacySchema =
|
|
2229
|
-
version:
|
|
2230
|
-
id:
|
|
2231
|
-
title:
|
|
2232
|
-
body:
|
|
2590
|
+
legacySchema = z5.object({
|
|
2591
|
+
version: z5.literal(1),
|
|
2592
|
+
id: z5.string().regex(/^[a-zA-Z0-9_-]+$/),
|
|
2593
|
+
title: z5.string().min(1),
|
|
2594
|
+
body: z5.string().min(1),
|
|
2233
2595
|
category: contentSchema.shape.category,
|
|
2234
2596
|
files: contentSchema.shape.files,
|
|
2235
|
-
createdAt:
|
|
2236
|
-
updatedAt:
|
|
2237
|
-
refreshedHash:
|
|
2238
|
-
status:
|
|
2239
|
-
supersededBy:
|
|
2597
|
+
createdAt: z5.string(),
|
|
2598
|
+
updatedAt: z5.string(),
|
|
2599
|
+
refreshedHash: z5.string(),
|
|
2600
|
+
status: z5.enum(["active", "superseded"]),
|
|
2601
|
+
supersededBy: z5.string().optional()
|
|
2240
2602
|
}).passthrough();
|
|
2241
2603
|
currentSchema = legacySchema.extend({
|
|
2242
|
-
version:
|
|
2604
|
+
version: z5.literal(2),
|
|
2243
2605
|
status: statusSchema,
|
|
2244
2606
|
approval: approvalSchema,
|
|
2245
|
-
revision:
|
|
2607
|
+
revision: z5.number().int().positive(),
|
|
2246
2608
|
owner: text(200).optional(),
|
|
2247
|
-
sources:
|
|
2248
|
-
history:
|
|
2609
|
+
sources: z5.array(decisionSourceSchema).max(20),
|
|
2610
|
+
history: z5.array(eventSchema).min(1)
|
|
2249
2611
|
}).superRefine((record, ctx) => {
|
|
2250
2612
|
const invalid = (message2) => ctx.addIssue({ code: "custom", message: message2 });
|
|
2251
2613
|
const same = (a, b) => JSON.stringify(a) === JSON.stringify(b);
|
|
@@ -2275,15 +2637,15 @@ var init_provenance = __esm({
|
|
|
2275
2637
|
}
|
|
2276
2638
|
if (!previous || !same(previous.content, decisionContent(record)) || previous.approval !== record.approval || previous.status !== record.status || previous.revision !== record.revision || previous.refreshedHash !== record.refreshedHash) invalid("Decision does not match the final history event");
|
|
2277
2639
|
});
|
|
2278
|
-
decisionSchema =
|
|
2640
|
+
decisionSchema = z5.union([legacySchema, currentSchema]);
|
|
2279
2641
|
}
|
|
2280
2642
|
});
|
|
2281
2643
|
|
|
2282
2644
|
// src/decisions/decisions.ts
|
|
2283
2645
|
import fs9 from "fs/promises";
|
|
2284
|
-
import
|
|
2285
|
-
import { createHash } from "crypto";
|
|
2286
|
-
import { z as
|
|
2646
|
+
import path18 from "path";
|
|
2647
|
+
import { createHash as createHash2 } from "crypto";
|
|
2648
|
+
import { z as z6 } from "zod";
|
|
2287
2649
|
async function loadDecisionStore(rootDir) {
|
|
2288
2650
|
const records = [];
|
|
2289
2651
|
const diagnostics = [];
|
|
@@ -2319,7 +2681,7 @@ var init_decisions = __esm({
|
|
|
2319
2681
|
TITLE_MAX_CHARS = 80;
|
|
2320
2682
|
BODY_RECOMMENDED_CHARS = 1500;
|
|
2321
2683
|
BODY_MAX_CHARS = 2500;
|
|
2322
|
-
boundedText = (field, max, hint) =>
|
|
2684
|
+
boundedText = (field, max, hint) => z6.string({
|
|
2323
2685
|
errorMap: (issue, ctx) => ({ message: issue.code === "too_big" ? `${field} has ${ctx.data.length} characters; maximum is ${max} (${ctx.data.length - max} over). ${hint}` : ctx.defaultError })
|
|
2324
2686
|
}).trim().min(1, `${field} must be non-empty`).max(max);
|
|
2325
2687
|
decisionTitleSchema = boundedText("title", TITLE_MAX_CHARS, "Tighten it to a specific headline.");
|
|
@@ -2332,9 +2694,9 @@ var init_decisions = __esm({
|
|
|
2332
2694
|
});
|
|
2333
2695
|
|
|
2334
2696
|
// src/decisions/drift.ts
|
|
2335
|
-
import
|
|
2697
|
+
import path19 from "path";
|
|
2336
2698
|
async function computeDecisionDrift(rootDir, decisions) {
|
|
2337
|
-
const resolvedRoot =
|
|
2699
|
+
const resolvedRoot = path19.resolve(rootDir);
|
|
2338
2700
|
const store = decisions ? { records: decisions, diagnostics: [] } : await loadDecisionStore(resolvedRoot);
|
|
2339
2701
|
const report = { historyAvailable: true, totalDecisions: store.records.length, staleDecisions: {}, freshness: {}, diagnostics: store.diagnostics };
|
|
2340
2702
|
const [head, workingTree] = await Promise.all([getCurrentGitHash(resolvedRoot), getWorkingTree(resolvedRoot)]);
|
|
@@ -2450,128 +2812,26 @@ var init_checks = __esm({
|
|
|
2450
2812
|
}
|
|
2451
2813
|
});
|
|
2452
2814
|
|
|
2453
|
-
// src/audit/findings.ts
|
|
2454
|
-
import { createHash as createHash2 } from "crypto";
|
|
2455
|
-
import { z as z5 } from "zod";
|
|
2456
|
-
function findingId(finding) {
|
|
2457
|
-
const e = finding.evidence;
|
|
2458
|
-
let key;
|
|
2459
|
-
switch (e.kind) {
|
|
2460
|
-
case "missing-path":
|
|
2461
|
-
key = e.resolvedPath && e.resolvedPath !== e.claimed ? [e.claimed, e.resolvedPath] : e.claimed;
|
|
2462
|
-
break;
|
|
2463
|
-
case "unmentioned-dir":
|
|
2464
|
-
key = e.dir;
|
|
2465
|
-
break;
|
|
2466
|
-
case "count-mismatch":
|
|
2467
|
-
key = [e.unit.replace(/s$/, ""), e.countedFrom];
|
|
2468
|
-
break;
|
|
2469
|
-
case "missing-script":
|
|
2470
|
-
key = e.scope && e.scope.directory !== "." ? [e.scriptName, e.scope.directory] : e.scriptName;
|
|
2471
|
-
break;
|
|
2472
|
-
case "doc-behind-manifests":
|
|
2473
|
-
key = null;
|
|
2474
|
-
break;
|
|
2475
|
-
case "decision-anchor":
|
|
2476
|
-
key = [e.decisionId, e.provenance?.revision, e.provenance?.approval];
|
|
2477
|
-
break;
|
|
2478
|
-
}
|
|
2479
|
-
return digest([finding.type, finding.anchor.doc, key]);
|
|
2480
|
-
}
|
|
2481
|
-
var digest, checkSchema, commitSchema, anchorSchema, count, scopeSchema, evidenceSchema, findingSchema, issueSchema, advisorySchema, checkResultSchema;
|
|
2482
|
-
var init_findings = __esm({
|
|
2483
|
-
"src/audit/findings.ts"() {
|
|
2484
|
-
"use strict";
|
|
2485
|
-
digest = (value) => createHash2("sha256").update(JSON.stringify(value)).digest("hex");
|
|
2486
|
-
checkSchema = z5.enum(["deleted-reference", "new-module", "stale-count", "dead-command", "deps-changed", "decision-anchor-drift"]);
|
|
2487
|
-
commitSchema = z5.object({ hash: z5.string().regex(/^[a-f0-9]{40,64}$/), date: z5.string(), subject: z5.string() });
|
|
2488
|
-
anchorSchema = z5.object({ doc: z5.string(), line: z5.number().int().positive().nullable(), excerpt: z5.string().nullable() });
|
|
2489
|
-
count = z5.number().int().nonnegative();
|
|
2490
|
-
scopeSchema = z5.object({
|
|
2491
|
-
basis: z5.enum(["repository", "document", "document-link", "explicit"]),
|
|
2492
|
-
directory: z5.string(),
|
|
2493
|
-
candidates: z5.array(z5.string())
|
|
2494
|
-
});
|
|
2495
|
-
evidenceSchema = z5.discriminatedUnion("kind", [
|
|
2496
|
-
z5.object({
|
|
2497
|
-
kind: z5.literal("missing-path"),
|
|
2498
|
-
claimed: z5.string(),
|
|
2499
|
-
renamedTo: z5.string().nullable(),
|
|
2500
|
-
deletedInCommit: commitSchema.nullable(),
|
|
2501
|
-
everTracked: z5.boolean(),
|
|
2502
|
-
parentDirExists: z5.boolean(),
|
|
2503
|
-
resolvedPath: z5.string().optional(),
|
|
2504
|
-
scope: scopeSchema.optional()
|
|
2505
|
-
}),
|
|
2506
|
-
z5.object({
|
|
2507
|
-
kind: z5.literal("unmentioned-dir"),
|
|
2508
|
-
dir: z5.string(),
|
|
2509
|
-
sourceFileCount: count,
|
|
2510
|
-
firstCommit: commitSchema.nullable(),
|
|
2511
|
-
checkedDocs: z5.array(z5.string())
|
|
2512
|
-
}),
|
|
2513
|
-
z5.object({
|
|
2514
|
-
kind: z5.literal("count-mismatch"),
|
|
2515
|
-
claimed: count,
|
|
2516
|
-
actual: count,
|
|
2517
|
-
unit: z5.string(),
|
|
2518
|
-
countedFrom: z5.string(),
|
|
2519
|
-
members: z5.array(z5.string())
|
|
2520
|
-
}),
|
|
2521
|
-
z5.object({
|
|
2522
|
-
kind: z5.literal("missing-script"),
|
|
2523
|
-
scriptName: z5.string(),
|
|
2524
|
-
invocation: z5.string(),
|
|
2525
|
-
manifestsChecked: z5.array(z5.string()),
|
|
2526
|
-
availableScripts: z5.array(z5.string()),
|
|
2527
|
-
scope: scopeSchema.optional()
|
|
2528
|
-
}),
|
|
2529
|
-
z5.object({
|
|
2530
|
-
kind: z5.literal("doc-behind-manifests"),
|
|
2531
|
-
docLastCommit: commitSchema,
|
|
2532
|
-
manifestCommits: z5.array(commitSchema.extend({ files: z5.array(z5.string()) })),
|
|
2533
|
-
totalCommits: count
|
|
2534
|
-
}),
|
|
2535
|
-
z5.object({
|
|
2536
|
-
kind: z5.literal("decision-anchor"),
|
|
2537
|
-
decisionId: z5.string(),
|
|
2538
|
-
title: z5.string(),
|
|
2539
|
-
changedFiles: z5.array(z5.string()),
|
|
2540
|
-
refreshedHash: z5.string(),
|
|
2541
|
-
provenance: z5.object({}).passthrough().optional()
|
|
2542
|
-
})
|
|
2543
|
-
]);
|
|
2544
|
-
findingSchema = z5.object({ message: z5.string(), anchor: anchorSchema, evidence: evidenceSchema });
|
|
2545
|
-
issueSchema = findingSchema.extend({
|
|
2546
|
-
type: z5.enum(["deleted-reference", "new-module", "stale-count", "dead-command"]),
|
|
2547
|
-
confidence: z5.enum(["certain", "likely"])
|
|
2548
|
-
});
|
|
2549
|
-
advisorySchema = findingSchema.extend({ type: checkSchema, resolution: z5.literal("recheck").optional() });
|
|
2550
|
-
checkResultSchema = z5.object({
|
|
2551
|
-
issues: z5.array(issueSchema),
|
|
2552
|
-
advisories: z5.array(advisorySchema),
|
|
2553
|
-
suppressedAdvisories: z5.array(advisorySchema).optional(),
|
|
2554
|
-
skipped: z5.array(z5.object({ check: z5.string(), reason: z5.string(), doc: z5.string().optional() }))
|
|
2555
|
-
});
|
|
2556
|
-
}
|
|
2557
|
-
});
|
|
2558
|
-
|
|
2559
2815
|
// src/audit/advisory-review.ts
|
|
2560
2816
|
import fs10 from "fs/promises";
|
|
2561
|
-
import { z as
|
|
2817
|
+
import { z as z7 } from "zod";
|
|
2562
2818
|
async function readRecord(root, id) {
|
|
2563
2819
|
const raw = await readStoreJson(root, recordPath(id));
|
|
2564
2820
|
if (raw === null) return null;
|
|
2565
2821
|
const record = recordSchema.parse(raw);
|
|
2566
2822
|
const { digest: stored, ...payload } = record;
|
|
2567
|
-
if (record.id !== id || digest(payload) !== stored || record.events.some((event) => findingId(event.finding) !== id || event.
|
|
2823
|
+
if (record.id !== id || digest(payload) !== stored || record.events.some((event) => findingId(event.finding) !== id || (event.outcome === "dismissed" ? !event.reasonCode || event.finding.type !== "deps-changed" : !event.reviewer || !!event.reasonCode) || event.evidence.fingerprint !== evidenceFingerprint(event.evidence, event.reasonCode))) {
|
|
2568
2824
|
throw new Error("Advisory review record is inconsistent or modified; inspect its history.");
|
|
2569
2825
|
}
|
|
2570
2826
|
return record;
|
|
2571
2827
|
}
|
|
2572
|
-
function
|
|
2828
|
+
function evidenceFingerprint(evidence, reason2) {
|
|
2829
|
+
return digest([evidence.scope, evidence.tree, ...reason2 === "no-dependency-claims" ? [] : [evidence.lastScopeCommit], evidence.paths]);
|
|
2830
|
+
}
|
|
2831
|
+
function scopes(finding, reason2) {
|
|
2573
2832
|
const files = [finding.anchor.doc], e = finding.evidence;
|
|
2574
2833
|
if (e.kind === "decision-anchor") throw new Error("Use review_decision for decision acceptance, reaffirmation or retirement.");
|
|
2834
|
+
if (reason2 === "no-dependency-claims" && e.kind === "doc-behind-manifests") return [`:(literal)${finding.anchor.doc}`];
|
|
2575
2835
|
if (e.kind === "doc-behind-manifests") return [`:(literal)${finding.anchor.doc}`, ...manifestPathspecs(finding.anchor.doc)];
|
|
2576
2836
|
if (e.kind === "missing-path") files.push(...e.scope?.candidates ?? [e.resolvedPath ?? e.claimed]);
|
|
2577
2837
|
else if (e.kind === "missing-script") files.push(...e.manifestsChecked, ...e.scope?.candidates ?? []);
|
|
@@ -2582,9 +2842,9 @@ function scopes(finding) {
|
|
|
2582
2842
|
return `:(literal)${file}`;
|
|
2583
2843
|
}).sort();
|
|
2584
2844
|
}
|
|
2585
|
-
async function reviewEvidence(root, finding) {
|
|
2845
|
+
async function reviewEvidence(root, finding, reason2) {
|
|
2586
2846
|
if (normalizeRepoPath(finding.anchor.doc) !== finding.anchor.doc) throw new Error("Invalid advisory document path.");
|
|
2587
|
-
const scope = scopes(finding);
|
|
2847
|
+
const scope = scopes(finding, reason2);
|
|
2588
2848
|
const head = (await git2(root, "rev-parse", "HEAD")).trim();
|
|
2589
2849
|
const tracked = (await git2(root, "ls-files", "--cached", "-z", "--", ...scope)).split("\0").filter(Boolean);
|
|
2590
2850
|
if (tracked.length > 2e4) throw new Error("Advisory scope exceeds 20,000 tracked files.");
|
|
@@ -2610,7 +2870,7 @@ async function reviewEvidence(root, finding) {
|
|
|
2610
2870
|
if (!tracked.includes(file) && await pathExists(root, file)) dirty = true;
|
|
2611
2871
|
}
|
|
2612
2872
|
}
|
|
2613
|
-
const fingerprint =
|
|
2873
|
+
const fingerprint = evidenceFingerprint({ head, scope, tree, lastScopeCommit, paths, fingerprint: "" }, reason2);
|
|
2614
2874
|
if ((await git2(root, "rev-parse", "HEAD")).trim() !== head) throw new Error("HEAD changed while collecting review evidence.");
|
|
2615
2875
|
return {
|
|
2616
2876
|
...evidenceSchema2.parse({ head, scope, tree, lastScopeCommit, paths, fingerprint }),
|
|
@@ -2657,7 +2917,7 @@ async function assessAdvisory(root, finding, baselineHead) {
|
|
|
2657
2917
|
const record = await readRecord(root, id);
|
|
2658
2918
|
if (!record) return null;
|
|
2659
2919
|
const event = record.events.at(-1);
|
|
2660
|
-
const current = await reviewEvidence(root, event.finding);
|
|
2920
|
+
const current = await reviewEvidence(root, event.finding, event.reasonCode);
|
|
2661
2921
|
await git2(root, "merge-base", "--is-ancestor", event.evidence.head, current.head);
|
|
2662
2922
|
const base = {
|
|
2663
2923
|
id,
|
|
@@ -2667,7 +2927,7 @@ async function assessAdvisory(root, finding, baselineHead) {
|
|
|
2667
2927
|
outcome: event.outcome,
|
|
2668
2928
|
reviewedHead: event.evidence.head
|
|
2669
2929
|
};
|
|
2670
|
-
if (current.dirty || current.fingerprint !== event.evidence.fingerprint || digest(scopes(finding)) !== digest(event.evidence.scope)) return { ...base, status: "reopened", reason: "Relevant document or repository evidence changed after the assessment; prepare a new review." };
|
|
2930
|
+
if (current.dirty || current.fingerprint !== event.evidence.fingerprint || digest(scopes(finding, event.reasonCode)) !== digest(event.evidence.scope)) return { ...base, status: "reopened", reason: "Relevant document or repository evidence changed after the assessment; prepare a new review." };
|
|
2671
2931
|
return { ...base, status: event.outcome === "deferred" ? "deferred" : "current", reason: event.outcome === "deferred" ? "The review was deferred and remains outstanding." : "An explicit assessment covers the current committed scope. This does not approve an engineering decision or certify correctness." };
|
|
2672
2932
|
} catch (error) {
|
|
2673
2933
|
return { id, status: "unverified", reason: error instanceof Error ? error.message : String(error) };
|
|
@@ -2684,7 +2944,7 @@ async function advisoryReviewInventory(root) {
|
|
|
2684
2944
|
if (names.length > 5e3) throw new Error("Advisory review inventory exceeds 5,000 records.");
|
|
2685
2945
|
return Promise.all(names.filter((name) => name.endsWith(".json")).sort().map(async (name) => [name, await readAuditInput(root, ".mason/reviews/advisories/" + name)]));
|
|
2686
2946
|
}
|
|
2687
|
-
var git2, hashSchema, commitSchema2, evidenceSchema2, eventSchema2, recordSchema, reviewSummarySchema, advisoryReviewRequest, recordPath;
|
|
2947
|
+
var git2, hashSchema, commitSchema2, evidenceSchema2, dismissalReasonSchema, eventSchema2, recordSchema, reviewSummarySchema, advisoryReviewRequest, recordPath;
|
|
2688
2948
|
var init_advisory_review = __esm({
|
|
2689
2949
|
"src/audit/advisory-review.ts"() {
|
|
2690
2950
|
"use strict";
|
|
@@ -2700,39 +2960,42 @@ var init_advisory_review = __esm({
|
|
|
2700
2960
|
init_decisions();
|
|
2701
2961
|
init_drift2();
|
|
2702
2962
|
git2 = async (root, ...args2) => (await (["log", "ls-tree", "ls-files", "merge-base"].includes(args2[0]) ? inspectionGit : execGit)(args2, { cwd: root, timeout: 1e4, maxBuffer: 8 * 1024 * 1024 })).stdout;
|
|
2703
|
-
hashSchema =
|
|
2704
|
-
commitSchema2 =
|
|
2705
|
-
evidenceSchema2 =
|
|
2963
|
+
hashSchema = z7.string().regex(/^[a-f0-9]{64}$/);
|
|
2964
|
+
commitSchema2 = z7.string().regex(/^[a-f0-9]{40,64}$/);
|
|
2965
|
+
evidenceSchema2 = z7.object({
|
|
2706
2966
|
head: commitSchema2,
|
|
2707
2967
|
fingerprint: hashSchema,
|
|
2708
|
-
scope:
|
|
2709
|
-
tree:
|
|
2968
|
+
scope: z7.array(z7.string()).min(1).max(1e3),
|
|
2969
|
+
tree: z7.string().max(2 * 1024 * 1024),
|
|
2710
2970
|
lastScopeCommit: commitSchema2,
|
|
2711
|
-
paths:
|
|
2971
|
+
paths: z7.array(z7.object({ path: z7.string(), exists: z7.boolean() })).max(1e3)
|
|
2712
2972
|
});
|
|
2713
|
-
|
|
2714
|
-
|
|
2715
|
-
|
|
2716
|
-
|
|
2717
|
-
|
|
2973
|
+
dismissalReasonSchema = z7.enum(["no-dependency-claims", "unrelated-manifest-change"]);
|
|
2974
|
+
eventSchema2 = z7.object({
|
|
2975
|
+
at: z7.string().datetime(),
|
|
2976
|
+
reviewer: z7.string().trim().min(1).max(200).optional(),
|
|
2977
|
+
note: z7.string().trim().min(1).max(2500),
|
|
2978
|
+
outcome: z7.enum(["addressed", "inapplicable", "deferred", "dismissed"]),
|
|
2979
|
+
reasonCode: dismissalReasonSchema.optional(),
|
|
2718
2980
|
finding: advisorySchema.refine((finding) => finding.evidence.kind !== "decision-anchor", "Decision findings use decision reviews"),
|
|
2719
2981
|
evidence: evidenceSchema2
|
|
2720
2982
|
});
|
|
2721
|
-
recordSchema =
|
|
2722
|
-
reviewSummarySchema =
|
|
2983
|
+
recordSchema = z7.object({ version: z7.literal(1), id: hashSchema, events: z7.array(eventSchema2).min(1).max(200), digest: hashSchema });
|
|
2984
|
+
reviewSummarySchema = z7.object({
|
|
2723
2985
|
id: hashSchema,
|
|
2724
|
-
status:
|
|
2725
|
-
reason:
|
|
2726
|
-
recordPath:
|
|
2727
|
-
reviewer:
|
|
2728
|
-
note:
|
|
2729
|
-
outcome:
|
|
2730
|
-
reviewedHead:
|
|
2986
|
+
status: z7.enum(["current", "reopened", "deferred", "unverified"]),
|
|
2987
|
+
reason: z7.string(),
|
|
2988
|
+
recordPath: z7.string().optional(),
|
|
2989
|
+
reviewer: z7.string().optional(),
|
|
2990
|
+
note: z7.string().optional(),
|
|
2991
|
+
outcome: z7.enum(["addressed", "inapplicable", "deferred", "accepted", "reaffirmed", "retired", "dismissed"]).optional(),
|
|
2992
|
+
reviewedHead: z7.string().optional()
|
|
2731
2993
|
});
|
|
2732
|
-
advisoryReviewRequest =
|
|
2733
|
-
baselinePath:
|
|
2994
|
+
advisoryReviewRequest = z7.object({
|
|
2995
|
+
baselinePath: z7.string().min(1),
|
|
2734
2996
|
findingId: hashSchema,
|
|
2735
|
-
action:
|
|
2997
|
+
action: z7.enum(["prepare", "addressed", "inapplicable", "deferred", "dismiss"]).default("prepare"),
|
|
2998
|
+
reasonCode: dismissalReasonSchema.optional(),
|
|
2736
2999
|
reviewer: eventSchema2.shape.reviewer.optional(),
|
|
2737
3000
|
note: eventSchema2.shape.note.optional(),
|
|
2738
3001
|
reviewToken: hashSchema.optional()
|
|
@@ -2743,12 +3006,12 @@ var init_advisory_review = __esm({
|
|
|
2743
3006
|
|
|
2744
3007
|
// src/audit/audit.ts
|
|
2745
3008
|
import fs11 from "fs/promises";
|
|
2746
|
-
import
|
|
3009
|
+
import path20 from "path";
|
|
2747
3010
|
async function computeAudit(rootDir, options = {}) {
|
|
2748
3011
|
return profilePhase("audit.current", () => auditCurrent(rootDir, options));
|
|
2749
3012
|
}
|
|
2750
3013
|
async function auditCurrent(rootDir, options) {
|
|
2751
|
-
const resolvedRoot =
|
|
3014
|
+
const resolvedRoot = path20.resolve(rootDir);
|
|
2752
3015
|
const [docs, headHash] = await Promise.all([discoverDocs(resolvedRoot), getCurrentGitHash(resolvedRoot)]);
|
|
2753
3016
|
if (docs.length === 0) return null;
|
|
2754
3017
|
const report = {
|
|
@@ -2782,7 +3045,7 @@ async function auditCurrent(rootDir, options) {
|
|
|
2782
3045
|
}
|
|
2783
3046
|
let decisionsPresent = false;
|
|
2784
3047
|
try {
|
|
2785
|
-
await fs11.access(
|
|
3048
|
+
await fs11.access(path20.join(resolvedRoot, ".mason", "decisions"));
|
|
2786
3049
|
decisionsPresent = true;
|
|
2787
3050
|
} catch {
|
|
2788
3051
|
}
|
|
@@ -2794,13 +3057,21 @@ async function auditCurrent(rootDir, options) {
|
|
|
2794
3057
|
changesSinceDoc,
|
|
2795
3058
|
decisionsPresent
|
|
2796
3059
|
};
|
|
3060
|
+
const policy = (await loadProjectConfig(resolvedRoot)).audit;
|
|
2797
3061
|
const selected = options.checks ?? ALL_CHECKS;
|
|
2798
3062
|
for (const name of ALL_CHECKS) {
|
|
2799
3063
|
if (!selected.includes(name)) continue;
|
|
2800
|
-
const
|
|
3064
|
+
const exclusions = policy?.exclude?.[name] ?? [];
|
|
3065
|
+
const included = await includedCheckPaths(resolvedRoot, docs.map((doc) => doc.path), exclusions);
|
|
3066
|
+
const excluded = docs.filter((doc) => !included.has(doc.path)).map((doc) => ({ check: name, doc: doc.path }));
|
|
3067
|
+
if (excluded.length) (report.excludedChecks ??= []).push(...excluded);
|
|
3068
|
+
const scoped = { ...ctx, docs: docs.filter((doc) => included.has(doc.path)) };
|
|
3069
|
+
const { issues, advisories, suppressedAdvisories, skipped } = await (options.runCheck ? options.runCheck(name, scoped) : CHECKS[name](scoped));
|
|
2801
3070
|
report.checksRun.push(name);
|
|
2802
3071
|
report.issues.push(...issues);
|
|
2803
|
-
|
|
3072
|
+
const advisoryPaths = await includedCheckPaths(resolvedRoot, [...new Set(advisories.map((f) => f.anchor.doc))], exclusions);
|
|
3073
|
+
report.advisories.push(...advisories.filter((f) => advisoryPaths.has(f.anchor.doc)));
|
|
3074
|
+
for (const f of advisories) if (!advisoryPaths.has(f.anchor.doc)) (report.excludedChecks ??= []).push({ check: name, doc: f.anchor.doc });
|
|
2804
3075
|
report.suppressedAdvisories.push(...suppressedAdvisories ?? []);
|
|
2805
3076
|
report.skippedChecks.push(...skipped);
|
|
2806
3077
|
}
|
|
@@ -2812,6 +3083,8 @@ async function auditCurrent(rootDir, options) {
|
|
|
2812
3083
|
var init_audit = __esm({
|
|
2813
3084
|
"src/audit/audit.ts"() {
|
|
2814
3085
|
"use strict";
|
|
3086
|
+
init_files();
|
|
3087
|
+
init_policy();
|
|
2815
3088
|
init_profile();
|
|
2816
3089
|
init_drift();
|
|
2817
3090
|
init_snapshot();
|
|
@@ -2824,9 +3097,9 @@ var init_audit = __esm({
|
|
|
2824
3097
|
|
|
2825
3098
|
// src/audit/repair.ts
|
|
2826
3099
|
import fs12 from "fs/promises";
|
|
2827
|
-
import
|
|
3100
|
+
import path21 from "path";
|
|
2828
3101
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
2829
|
-
import { z as
|
|
3102
|
+
import { z as z8 } from "zod";
|
|
2830
3103
|
function recheckable(finding) {
|
|
2831
3104
|
return !("confidence" in finding) && finding.resolution === "recheck" && ["deleted-reference", "dead-command", "new-module"].includes(finding.type) && ["missing-path", "missing-script", "unmentioned-dir"].includes(finding.evidence.kind);
|
|
2832
3105
|
}
|
|
@@ -2845,7 +3118,7 @@ async function docState(root) {
|
|
|
2845
3118
|
docs.push([doc, null]);
|
|
2846
3119
|
}
|
|
2847
3120
|
}
|
|
2848
|
-
return digest(docs);
|
|
3121
|
+
return digest([docs, (await loadProjectConfig(root)).audit]);
|
|
2849
3122
|
}
|
|
2850
3123
|
async function stableAudit(root, checks, options = {}) {
|
|
2851
3124
|
return profilePhase("repair.audit", () => readStableAudit(root, checks, options));
|
|
@@ -2894,7 +3167,7 @@ async function withRepairSession(root, options, run, validate, cachedAudit) {
|
|
|
2894
3167
|
}
|
|
2895
3168
|
async function prepareOriginal(rootDir, checks, options, session) {
|
|
2896
3169
|
const root = await fs12.realpath(rootDir);
|
|
2897
|
-
const selected =
|
|
3170
|
+
const selected = z8.array(checkSchema).nonempty().parse(checks);
|
|
2898
3171
|
const report = await (session ? session.audit(selected) : stableAudit(root, selected, options));
|
|
2899
3172
|
if (!report) throw new Error("No context files found to prepare a repair.");
|
|
2900
3173
|
if (!report.gitAvailable) throw new Error("Readable Git history is required to prepare a repair.");
|
|
@@ -2911,8 +3184,8 @@ async function prepareOriginal(rootDir, checks, options, session) {
|
|
|
2911
3184
|
}
|
|
2912
3185
|
async function loadRepairBaseline(rootDir, baselinePath) {
|
|
2913
3186
|
const root = await fs12.realpath(rootDir);
|
|
2914
|
-
const declaredRoot =
|
|
2915
|
-
const relative =
|
|
3187
|
+
const declaredRoot = path21.resolve(rootDir);
|
|
3188
|
+
const relative = path21.isAbsolute(baselinePath) ? path21.relative(isWithinRoot(declaredRoot, baselinePath) ? declaredRoot : root, baselinePath) : baselinePath;
|
|
2916
3189
|
const stored = baselineSchema.parse(await readStoreJson(root, relative));
|
|
2917
3190
|
const { digest: savedDigest, ...payload } = stored;
|
|
2918
3191
|
if (digest(payload) !== savedDigest) throw new Error("Repair baseline was modified; use the original baseline.");
|
|
@@ -2958,6 +3231,9 @@ async function verifyOriginal(rootDir, baselinePath, options, session) {
|
|
|
2958
3231
|
if (now && ("confidence" in finding || "confidence" in now)) {
|
|
2959
3232
|
return { ...base, status: "unresolved", reason: "The original check still reports this claim." };
|
|
2960
3233
|
}
|
|
3234
|
+
if (current.excludedChecks?.some((excluded) => excluded.check === finding.type && excluded.doc === finding.anchor.doc)) {
|
|
3235
|
+
return { ...base, status: "resolved", reason: "Excluded by project audit policy; this is not a correctness assessment. Original evidence is retained." };
|
|
3236
|
+
}
|
|
2961
3237
|
const skipped = current.skippedChecks.filter((s) => s.check === finding.type && (!s.doc || s.doc === finding.anchor.doc));
|
|
2962
3238
|
if (!current.checksRun?.includes(finding.type) || skipped.length) {
|
|
2963
3239
|
return { ...base, status: "unverified", reason: skipped.map((s) => s.reason).join("; ") || "The original check did not run." };
|
|
@@ -2968,6 +3244,9 @@ async function verifyOriginal(rootDir, baselinePath, options, session) {
|
|
|
2968
3244
|
if (review.status === "current") return { ...base, review, status: "resolved", reason: review.reason };
|
|
2969
3245
|
return { ...base, review, status: "review-required", reason: review.reason };
|
|
2970
3246
|
}
|
|
3247
|
+
if (!("confidence" in finding) && finding.evidence.kind === "doc-behind-manifests" && !finding.evidence.matches?.length && (!now || !("confidence" in now) && now.resolution === "informational")) {
|
|
3248
|
+
return { ...base, status: "resolved", reason: "Manifest recency alone establishes no affected passage. Retained as information; no assessment is required." };
|
|
3249
|
+
}
|
|
2971
3250
|
if (!("confidence" in finding) && (!recheckable(finding) || now)) {
|
|
2972
3251
|
return {
|
|
2973
3252
|
...base,
|
|
@@ -2996,7 +3275,7 @@ async function verifyOriginal(rootDir, baselinePath, options, session) {
|
|
|
2996
3275
|
const reviewedIds = new Set(current?.advisoryReviews?.filter((review) => review.status === "current").map((review) => review.id));
|
|
2997
3276
|
const counts = { resolved: 0, unresolved: 0, "review-required": 0, unverified: 0 };
|
|
2998
3277
|
for (const f of findings) counts[f.status]++;
|
|
2999
|
-
const incomplete = diagnostics.length > 0 || counts.unverified > 0 || counts["review-required"] > 0 || (current?.skippedChecks.length ?? 0) > 0 || newFindings.some((f) => !("confidence" in f) && !reviewedIds.has(findingId(f)));
|
|
3278
|
+
const incomplete = diagnostics.length > 0 || counts.unverified > 0 || counts["review-required"] > 0 || (current?.skippedChecks.length ?? 0) > 0 || newFindings.some((f) => !("confidence" in f) && f.resolution !== "informational" && !reviewedIds.has(findingId(f)));
|
|
3000
3279
|
const issuesRemain = counts.unresolved > 0 || newFindings.some((f) => "confidence" in f);
|
|
3001
3280
|
return {
|
|
3002
3281
|
version: 1,
|
|
@@ -3029,41 +3308,42 @@ var init_repair = __esm({
|
|
|
3029
3308
|
init_findings();
|
|
3030
3309
|
init_advisory_review();
|
|
3031
3310
|
init_findings();
|
|
3032
|
-
count2 =
|
|
3033
|
-
reportSchema =
|
|
3034
|
-
version:
|
|
3035
|
-
root:
|
|
3036
|
-
gitAvailable:
|
|
3311
|
+
count2 = z8.number().int().nonnegative();
|
|
3312
|
+
reportSchema = z8.object({
|
|
3313
|
+
version: z8.literal(1),
|
|
3314
|
+
root: z8.string(),
|
|
3315
|
+
gitAvailable: z8.literal(true),
|
|
3037
3316
|
headHash: commitSchema.shape.hash,
|
|
3038
|
-
checksRun:
|
|
3039
|
-
docs:
|
|
3040
|
-
path:
|
|
3041
|
-
scope:
|
|
3042
|
-
kind:
|
|
3317
|
+
checksRun: z8.array(checkSchema).nonempty(),
|
|
3318
|
+
docs: z8.array(z8.object({
|
|
3319
|
+
path: z8.string().refine((value) => normalizeRepoPath(value) === value),
|
|
3320
|
+
scope: z8.string().optional(),
|
|
3321
|
+
kind: z8.enum(["instructions", "readme", "guide"]).optional(),
|
|
3043
3322
|
lastCommit: commitSchema.nullable(),
|
|
3044
|
-
dirty:
|
|
3323
|
+
dirty: z8.boolean(),
|
|
3045
3324
|
lineCount: count2
|
|
3046
3325
|
})).nonempty(),
|
|
3047
|
-
decisionsChecked:
|
|
3048
|
-
clean:
|
|
3049
|
-
|
|
3050
|
-
|
|
3051
|
-
|
|
3052
|
-
|
|
3326
|
+
decisionsChecked: z8.boolean(),
|
|
3327
|
+
clean: z8.boolean(),
|
|
3328
|
+
excludedChecks: z8.array(z8.object({ check: checkSchema, doc: z8.string() })).optional(),
|
|
3329
|
+
issues: z8.array(issueSchema),
|
|
3330
|
+
advisories: z8.array(advisorySchema),
|
|
3331
|
+
suppressedAdvisories: z8.array(advisorySchema).optional(),
|
|
3332
|
+
skippedChecks: z8.array(z8.object({ check: z8.string(), reason: z8.string(), doc: z8.string().optional() }))
|
|
3053
3333
|
});
|
|
3054
|
-
baselineSchema =
|
|
3055
|
-
kind:
|
|
3056
|
-
version:
|
|
3057
|
-
createdAt:
|
|
3334
|
+
baselineSchema = z8.object({
|
|
3335
|
+
kind: z8.literal("mason-audit-repair"),
|
|
3336
|
+
version: z8.literal(1),
|
|
3337
|
+
createdAt: z8.string().datetime(),
|
|
3058
3338
|
report: reportSchema,
|
|
3059
|
-
digest:
|
|
3339
|
+
digest: z8.string().regex(/^[a-f0-9]{64}$/)
|
|
3060
3340
|
});
|
|
3061
|
-
repairFindingSchema =
|
|
3062
|
-
id:
|
|
3063
|
-
original:
|
|
3064
|
-
status:
|
|
3065
|
-
reason:
|
|
3066
|
-
current:
|
|
3341
|
+
repairFindingSchema = z8.object({
|
|
3342
|
+
id: z8.string(),
|
|
3343
|
+
original: z8.union([issueSchema, advisorySchema]),
|
|
3344
|
+
status: z8.enum(["resolved", "unresolved", "review-required", "unverified"]),
|
|
3345
|
+
reason: z8.string(),
|
|
3346
|
+
current: z8.union([issueSchema, advisorySchema]).optional(),
|
|
3067
3347
|
review: reviewSummarySchema.optional()
|
|
3068
3348
|
});
|
|
3069
3349
|
}
|
|
@@ -3071,10 +3351,10 @@ var init_repair = __esm({
|
|
|
3071
3351
|
|
|
3072
3352
|
// src/automation/evidence.ts
|
|
3073
3353
|
import fs13 from "fs/promises";
|
|
3074
|
-
import
|
|
3354
|
+
import path22 from "path";
|
|
3075
3355
|
import { createHash as createHash3 } from "crypto";
|
|
3076
|
-
import
|
|
3077
|
-
import { z as
|
|
3356
|
+
import fg7 from "fast-glob";
|
|
3357
|
+
import { z as z9 } from "zod";
|
|
3078
3358
|
async function git3(root, ...args2) {
|
|
3079
3359
|
return profilePhase("automation.git", async () => (await execGit(args2, { cwd: root, maxBuffer: 16 * 1024 * 1024, timeout: 1e4 })).stdout);
|
|
3080
3360
|
}
|
|
@@ -3091,6 +3371,11 @@ async function readInputs(root) {
|
|
|
3091
3371
|
}
|
|
3092
3372
|
async function collectInputs(root) {
|
|
3093
3373
|
const docPaths = await discoverDocPaths(root);
|
|
3374
|
+
const auditPolicy = (await loadProjectConfig(root)).audit;
|
|
3375
|
+
const allowed = /* @__PURE__ */ new Map();
|
|
3376
|
+
for (const name of ["deleted-reference", "new-module", "stale-count", "dead-command"]) {
|
|
3377
|
+
allowed.set(name, await includedCheckPaths(root, docPaths, auditPolicy?.exclude?.[name] ?? []));
|
|
3378
|
+
}
|
|
3094
3379
|
const [headText, docStatus, shallowPath, replacements] = await Promise.all([
|
|
3095
3380
|
git3(root, "rev-parse", "HEAD"),
|
|
3096
3381
|
docPaths.length ? git3(root, "status", "--porcelain=v1", "-z", "--untracked-files=all", "--", ...docPaths.map((p) => `:(literal)${p}`)) : "",
|
|
@@ -3100,7 +3385,7 @@ async function collectInputs(root) {
|
|
|
3100
3385
|
const head = headText.trim();
|
|
3101
3386
|
let shallow = null;
|
|
3102
3387
|
try {
|
|
3103
|
-
shallow = await fs13.readFile(
|
|
3388
|
+
shallow = await fs13.readFile(path22.resolve(root, shallowPath.trim()), "utf8");
|
|
3104
3389
|
} catch (error) {
|
|
3105
3390
|
if (error.code !== "ENOENT") throw error;
|
|
3106
3391
|
}
|
|
@@ -3119,18 +3404,18 @@ async function collectInputs(root) {
|
|
|
3119
3404
|
docContents.push([file, text2]);
|
|
3120
3405
|
const parsed = { path: file, content: text2 ?? "", claims: extractClaims(text2 ?? "") };
|
|
3121
3406
|
parsedDocs.push(parsed);
|
|
3122
|
-
for (const claim of parsed.claims.paths) {
|
|
3407
|
+
for (const claim of allowed.get("deleted-reference").has(file) ? parsed.claims.paths : []) {
|
|
3123
3408
|
const scope = pathClaimScope(file, claim);
|
|
3124
3409
|
if (scope?.candidates.some(gitMetadataPath)) continue;
|
|
3125
3410
|
for (const candidate of scope?.candidates ?? []) {
|
|
3126
3411
|
if (optionalMasonPath(candidate)) continue;
|
|
3127
|
-
const [filePresent, parentPresent] = await Promise.all([exists(candidate), exists(
|
|
3412
|
+
const [filePresent, parentPresent] = await Promise.all([exists(candidate), exists(path22.posix.dirname(candidate))]);
|
|
3128
3413
|
claims.push([candidate, filePresent, parentPresent]);
|
|
3129
3414
|
}
|
|
3130
3415
|
}
|
|
3131
3416
|
}
|
|
3132
|
-
const combinedDocs = moduleDocumentation(parsedDocs);
|
|
3133
|
-
const countClaims = parsedDocs.flatMap((doc) => doc.claims.counts.map((claim) => ({ doc: doc.path, claim })));
|
|
3417
|
+
const combinedDocs = moduleDocumentation(parsedDocs.filter((doc) => allowed.get("new-module").has(doc.path)));
|
|
3418
|
+
const countClaims = parsedDocs.filter((doc) => allowed.get("stale-count").has(doc.path)).flatMap((doc) => doc.claims.counts.map((claim) => ({ doc: doc.path, claim })));
|
|
3134
3419
|
const decisionDirectory = await storePath(root, ".mason/decisions");
|
|
3135
3420
|
const decisionPresence = await fs13.lstat(decisionDirectory).then((stat) => stat.isDirectory() ? "directory" : "file", (error) => {
|
|
3136
3421
|
if (error.code === "ENOENT") return "absent";
|
|
@@ -3139,8 +3424,8 @@ async function collectInputs(root) {
|
|
|
3139
3424
|
const [modules, counts, commands2, decisionFiles] = await Promise.all([
|
|
3140
3425
|
combinedDocs ? moduleCandidates(root, combinedDocs) : [],
|
|
3141
3426
|
Promise.all(countClaims.map(({ doc, claim }) => resolveDocCountSource(root, doc, claim))),
|
|
3142
|
-
commandInputs(root, parsedDocs),
|
|
3143
|
-
|
|
3427
|
+
commandInputs(root, parsedDocs.filter((doc) => allowed.get("dead-command").has(doc.path))),
|
|
3428
|
+
fg7(".mason/decisions/*.json", { cwd: root, dot: true, onlyFiles: false, followSymbolicLinks: false })
|
|
3144
3429
|
]);
|
|
3145
3430
|
const decisions = await Promise.all(decisionFiles.sort().map(async (file) => {
|
|
3146
3431
|
await storePath(root, file);
|
|
@@ -3150,7 +3435,7 @@ async function collectInputs(root) {
|
|
|
3150
3435
|
git3(root, "status", "--porcelain=v1", "-z", "--untracked-files=all", "--", ".", ":(exclude).mason/reports"),
|
|
3151
3436
|
git3(root, "ls-files", "--stage", "-z", "--", ".", ":(exclude).mason/reports")
|
|
3152
3437
|
]) : ["", ""];
|
|
3153
|
-
const common = [
|
|
3438
|
+
const common = [7, engineVersion, head, shallow, replacements, docContents, auditPolicy];
|
|
3154
3439
|
const keys = {
|
|
3155
3440
|
"deleted-reference": hash([common, claims, docStatus]),
|
|
3156
3441
|
"new-module": hash([common, modules]),
|
|
@@ -3197,6 +3482,8 @@ var engineVersion, hash, content, cacheSchema;
|
|
|
3197
3482
|
var init_evidence = __esm({
|
|
3198
3483
|
"src/automation/evidence.ts"() {
|
|
3199
3484
|
"use strict";
|
|
3485
|
+
init_policy();
|
|
3486
|
+
init_files();
|
|
3200
3487
|
init_git_read();
|
|
3201
3488
|
init_profile();
|
|
3202
3489
|
init_docs();
|
|
@@ -3210,10 +3497,10 @@ var init_evidence = __esm({
|
|
|
3210
3497
|
init_dead_command();
|
|
3211
3498
|
init_storage();
|
|
3212
3499
|
init_advisory_review();
|
|
3213
|
-
engineVersion = true ? "0.19.
|
|
3500
|
+
engineVersion = true ? "0.19.2" : "development";
|
|
3214
3501
|
hash = (value) => createHash3("sha256").update(JSON.stringify(value)).digest("hex");
|
|
3215
3502
|
content = readAuditInput;
|
|
3216
|
-
cacheSchema =
|
|
3503
|
+
cacheSchema = z9.object({ version: z9.literal(1), entries: z9.record(z9.object({ key: z9.string(), result: checkResultSchema })), digest: z9.string() });
|
|
3217
3504
|
}
|
|
3218
3505
|
});
|
|
3219
3506
|
|
|
@@ -3221,7 +3508,7 @@ var init_evidence = __esm({
|
|
|
3221
3508
|
import os2 from "os";
|
|
3222
3509
|
import fs14 from "fs/promises";
|
|
3223
3510
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
3224
|
-
import { z as
|
|
3511
|
+
import { z as z10 } from "zod";
|
|
3225
3512
|
function parseExecution(raw) {
|
|
3226
3513
|
try {
|
|
3227
3514
|
return executionSchema.parse(raw);
|
|
@@ -3239,7 +3526,7 @@ function automationFailure(error) {
|
|
|
3239
3526
|
codes.add(String(cause.code));
|
|
3240
3527
|
cause = cause.cause;
|
|
3241
3528
|
}
|
|
3242
|
-
const code = codes.has("ENOSPC") || codes.has("EDQUOT") ? "storage-full" : /changed during|changed while|changed between/.test(message2) ? "inputs-changed" : /Automation is busy/.test(message2) ? "busy" : /not a git repository|unknown revision|bad revision|different history|unreachable/.test(message2) ? "history-unavailable" : error instanceof
|
|
3529
|
+
const code = codes.has("ENOSPC") || codes.has("EDQUOT") ? "storage-full" : /changed during|changed while|changed between/.test(message2) ? "inputs-changed" : /Automation is busy/.test(message2) ? "busy" : /not a git repository|unknown revision|bad revision|different history|unreachable/.test(message2) ? "history-unavailable" : error instanceof z10.ZodError || error instanceof SyntaxError || /Hook input|Expected one command|Unknown automation command|--host/.test(message2) ? "invalid-input" : /store|baseline|modified|symbolic link|Symlink|automation state|state belongs/.test(message2) ? "invalid-evidence" : [...codes].some((c) => /^E[A-Z]+$/.test(c)) ? "io-error" : "internal";
|
|
3243
3530
|
return { code, message: message2, retryable: ["inputs-changed", "storage-full", "busy", "io-error"].includes(code), receiptRecorded: false };
|
|
3244
3531
|
}
|
|
3245
3532
|
function failureMessage(error) {
|
|
@@ -3368,41 +3655,103 @@ var init_execution = __esm({
|
|
|
3368
3655
|
init_store();
|
|
3369
3656
|
init_evidence();
|
|
3370
3657
|
init_storage();
|
|
3371
|
-
failureSchema =
|
|
3372
|
-
code:
|
|
3373
|
-
message:
|
|
3374
|
-
retryable:
|
|
3375
|
-
receiptRecorded:
|
|
3658
|
+
failureSchema = z10.object({
|
|
3659
|
+
code: z10.enum(["inputs-changed", "storage-full", "busy", "invalid-input", "history-unavailable", "invalid-evidence", "io-error", "internal"]),
|
|
3660
|
+
message: z10.string(),
|
|
3661
|
+
retryable: z10.boolean(),
|
|
3662
|
+
receiptRecorded: z10.boolean()
|
|
3376
3663
|
});
|
|
3377
|
-
attemptSchema =
|
|
3378
|
-
id:
|
|
3379
|
-
lease:
|
|
3380
|
-
event:
|
|
3381
|
-
startedAt:
|
|
3382
|
-
finishedAt:
|
|
3383
|
-
durationMs:
|
|
3384
|
-
pid:
|
|
3385
|
-
host:
|
|
3386
|
-
status:
|
|
3387
|
-
verificationStatus:
|
|
3388
|
-
reportPath:
|
|
3664
|
+
attemptSchema = z10.object({
|
|
3665
|
+
id: z10.string().regex(/^[a-zA-Z0-9_-]+$/),
|
|
3666
|
+
lease: z10.literal(true).optional(),
|
|
3667
|
+
event: z10.string(),
|
|
3668
|
+
startedAt: z10.string(),
|
|
3669
|
+
finishedAt: z10.string().optional(),
|
|
3670
|
+
durationMs: z10.number().nonnegative().optional(),
|
|
3671
|
+
pid: z10.number().int().positive(),
|
|
3672
|
+
host: z10.string(),
|
|
3673
|
+
status: z10.enum(["running", "completed", "failed", "unknown"]),
|
|
3674
|
+
verificationStatus: z10.string().optional(),
|
|
3675
|
+
reportPath: z10.string().optional(),
|
|
3389
3676
|
failure: failureSchema.optional()
|
|
3390
3677
|
});
|
|
3391
|
-
executionSchema =
|
|
3392
|
-
notificationsSchema =
|
|
3393
|
-
signature:
|
|
3678
|
+
executionSchema = z10.object({ version: z10.literal(1), attempts: z10.array(attemptSchema).max(160), discardedAttempts: z10.number().int().nonnegative().default(0) });
|
|
3679
|
+
notificationsSchema = z10.object({ version: z10.literal(1), sessions: z10.record(z10.object({
|
|
3680
|
+
signature: z10.string(),
|
|
3394
3681
|
code: failureSchema.shape.code,
|
|
3395
|
-
message:
|
|
3396
|
-
count:
|
|
3397
|
-
lastAt:
|
|
3682
|
+
message: z10.string(),
|
|
3683
|
+
count: z10.number().int().positive(),
|
|
3684
|
+
lastAt: z10.string()
|
|
3398
3685
|
})) });
|
|
3399
3686
|
leasePath = (directory, id) => `${directory}/executions/${id}.json`;
|
|
3400
3687
|
}
|
|
3401
3688
|
});
|
|
3402
3689
|
|
|
3690
|
+
// src/decisions/pending.ts
|
|
3691
|
+
var pending_exports = {};
|
|
3692
|
+
__export(pending_exports, {
|
|
3693
|
+
pendingKnowledge: () => pendingKnowledge
|
|
3694
|
+
});
|
|
3695
|
+
async function pendingKnowledge(root, now = Date.now()) {
|
|
3696
|
+
try {
|
|
3697
|
+
const [store, status] = await Promise.all([
|
|
3698
|
+
loadDecisionStore(root),
|
|
3699
|
+
execGit(
|
|
3700
|
+
["status", "--porcelain=v1", "-z", "--untracked-files=all", "--", ".mason/decisions"],
|
|
3701
|
+
{ cwd: root, timeout: 1e4, maxBuffer: 1024 * 1024 }
|
|
3702
|
+
)
|
|
3703
|
+
]);
|
|
3704
|
+
const changed = /* @__PURE__ */ new Set();
|
|
3705
|
+
const entries = status.stdout.split("\0");
|
|
3706
|
+
for (let i = 0; i < entries.length; i++) {
|
|
3707
|
+
const entry = entries[i];
|
|
3708
|
+
if (!entry) continue;
|
|
3709
|
+
const file = entry.slice(3);
|
|
3710
|
+
if (/^\.mason\/decisions\/[^/]+\.json$/.test(file)) changed.add(file);
|
|
3711
|
+
if (/[RC]/.test(entry.slice(0, 2))) i++;
|
|
3712
|
+
}
|
|
3713
|
+
const records = new Map(store.records.map((record) => [`.mason/decisions/${record.id}.json`, record]));
|
|
3714
|
+
const age = (at) => {
|
|
3715
|
+
const time = at ? Date.parse(at) : NaN;
|
|
3716
|
+
return Number.isFinite(time) ? `${Math.max(0, Math.floor((now - time) / 864e5))} days` : "unknown age";
|
|
3717
|
+
};
|
|
3718
|
+
const safe = (value) => value.replace(/[\u0000-\u001f\u007f-\u009f]/g, " ");
|
|
3719
|
+
const list = (items) => items.slice(0, 5).map(safe).join("; ") + (items.length > 5 ? `; ${items.length - 5} more` : "");
|
|
3720
|
+
const proposals = store.records.filter((record) => record.status === "active" && decisionApproval(record) === "proposed").map((record) => {
|
|
3721
|
+
const history = record.version === 2 ? record.history : [];
|
|
3722
|
+
let lastReview = -1;
|
|
3723
|
+
history.forEach((event, index2) => {
|
|
3724
|
+
if (event.kind === "accepted" || event.kind === "reaffirmed") lastReview = index2;
|
|
3725
|
+
});
|
|
3726
|
+
const since = history.slice(lastReview + 1).find((event) => event.kind === "created" || event.kind === "revised")?.at ?? record.createdAt;
|
|
3727
|
+
return { record, since };
|
|
3728
|
+
}).sort((a, b) => {
|
|
3729
|
+
const timestamp = (value) => Number.isFinite(Date.parse(value)) ? Date.parse(value) : Infinity;
|
|
3730
|
+
return timestamp(a.since) - timestamp(b.since) || a.record.id.localeCompare(b.record.id);
|
|
3731
|
+
});
|
|
3732
|
+
const lines = [];
|
|
3733
|
+
if (changed.size) lines.push(`${changed.size} uncommitted decision record(s): ${list([...changed].sort().map((file) => `${file} (saved-record age: ${age(records.get(file)?.updatedAt)})`))}.`);
|
|
3734
|
+
if (proposals.length) lines.push(`${proposals.length} proposal(s) awaiting review (oldest first): ${list(proposals.map(({ record, since }) => `.mason/decisions/${record.id}.json (${age(since)})`))}.`);
|
|
3735
|
+
if (store.diagnostics.length) lines.push(`${store.diagnostics.length} decision record(s) could not be read; pending knowledge summary is incomplete.`);
|
|
3736
|
+
return lines.length ? `Mason pending knowledge:
|
|
3737
|
+
${lines.join("\n")}
|
|
3738
|
+
Informational only; review and commit through the normal project workflow. No approval is implied.` : null;
|
|
3739
|
+
} catch {
|
|
3740
|
+
return "Mason pending knowledge: unavailable; decision records or Git status could not be read.";
|
|
3741
|
+
}
|
|
3742
|
+
}
|
|
3743
|
+
var init_pending = __esm({
|
|
3744
|
+
"src/decisions/pending.ts"() {
|
|
3745
|
+
"use strict";
|
|
3746
|
+
init_git_read();
|
|
3747
|
+
init_decisions();
|
|
3748
|
+
init_provenance();
|
|
3749
|
+
}
|
|
3750
|
+
});
|
|
3751
|
+
|
|
3403
3752
|
// src/automation/runtime.ts
|
|
3404
3753
|
import { randomUUID as randomUUID4 } from "crypto";
|
|
3405
|
-
import { z as
|
|
3754
|
+
import { z as z11 } from "zod";
|
|
3406
3755
|
async function observeReadOnlyTool(dir, event) {
|
|
3407
3756
|
const ws = await profilePhase("automation.workspace", () => workspace(dir));
|
|
3408
3757
|
await withLock(ws.root, ws.directory, async () => {
|
|
@@ -3624,6 +3973,19 @@ async function automate(dir, event) {
|
|
|
3624
3973
|
const state = await loadState(ws);
|
|
3625
3974
|
if (state.analysis?.id !== result.analysis.id) throw new Error("Automation evidence changed during publication; retry against the current inputs.");
|
|
3626
3975
|
const report = structuredClone(result.analysis.report);
|
|
3976
|
+
if (!state.notifications) {
|
|
3977
|
+
let baseline = { findings: report.findings, head: report.head };
|
|
3978
|
+
if (state.latest) {
|
|
3979
|
+
try {
|
|
3980
|
+
const previous = z11.object({ findings: z11.array(repairFindingSchema), head: z11.string() }).safeParse(await readStoreJson(ws.root, state.latest));
|
|
3981
|
+
if (!previous.success) throw new Error("Unsupported saved report");
|
|
3982
|
+
baseline = previous.data;
|
|
3983
|
+
} catch {
|
|
3984
|
+
report.diagnostics.push("The saved notification baseline could not be read; initialized from the current audit. Original repair evidence is retained. Run an explicit check for all outstanding findings.");
|
|
3985
|
+
}
|
|
3986
|
+
}
|
|
3987
|
+
state.notifications = createNotificationState(baseline.findings, baseline.head);
|
|
3988
|
+
}
|
|
3627
3989
|
if (result.shared) report.checks = { ...report.checks, ran: [], reused: [.../* @__PURE__ */ new Set([...report.checks.ran, ...report.checks.reused])], shared: true };
|
|
3628
3990
|
report.reportPath = ws.directory + "/checks/" + randomUUID4() + ".json";
|
|
3629
3991
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -3636,12 +3998,8 @@ async function automate(dir, event) {
|
|
|
3636
3998
|
if (Object.keys(state.sessions).length >= 128) throw new Error("Too many sessions with unfinished tool calls; original evidence was retained.");
|
|
3637
3999
|
state.sessions[key] = {
|
|
3638
4000
|
host: event.host,
|
|
3639
|
-
|
|
3640
|
-
continued: false,
|
|
3641
|
-
initialIssues: report.findings.filter((f) => f.status === "unresolved").map((f) => f.id),
|
|
3642
|
-
initialDocs: inputs.docs,
|
|
4001
|
+
notifiedDiagnostics: [],
|
|
3643
4002
|
lastUsed: now,
|
|
3644
|
-
mutationObserved: false,
|
|
3645
4003
|
pending: {},
|
|
3646
4004
|
coverageGaps: [],
|
|
3647
4005
|
events: {}
|
|
@@ -3656,7 +4014,6 @@ async function automate(dir, event) {
|
|
|
3656
4014
|
session.pending[event.toolId] = inputs.fingerprint;
|
|
3657
4015
|
}
|
|
3658
4016
|
if (event.event === "after_tool" && event.mutating) {
|
|
3659
|
-
session.mutationObserved = true;
|
|
3660
4017
|
if (!event.toolId || !session.pending[event.toolId]) {
|
|
3661
4018
|
const gap = "A tool completed without an observed matching pre-tool capture in this checkout; pre-edit coverage is unknown. The tool may have changed checkouts; captures are kept separately for each worktree.";
|
|
3662
4019
|
if (!session.coverageGaps.includes(gap)) session.coverageGaps.push(gap);
|
|
@@ -3667,31 +4024,39 @@ async function automate(dir, event) {
|
|
|
3667
4024
|
if (session.coverageGaps.length && report.status !== "unavailable") report.status = "incomplete";
|
|
3668
4025
|
report.capture = report.status !== "unavailable" && !session.coverageGaps.length && (session.events.session_start || session.events.before_tool) ? "observed" : "unknown";
|
|
3669
4026
|
}
|
|
3670
|
-
const
|
|
3671
|
-
|
|
3672
|
-
const
|
|
3673
|
-
const
|
|
3674
|
-
if (session)
|
|
3675
|
-
session.
|
|
3676
|
-
|
|
3677
|
-
|
|
3678
|
-
const
|
|
4027
|
+
const selected = session && event.event === "task_end" ? completionFindings(state.notifications, report.findings) : [];
|
|
4028
|
+
state.agentNotifications ??= { ...structuredClone(state.notifications), delivered: {} };
|
|
4029
|
+
const feedback = session && ["turn_start", "after_tool"].includes(event.event) ? completionFindings(state.agentNotifications, report.findings) : [];
|
|
4030
|
+
const diagnostics = session ? report.diagnostics.filter((d) => !session.notifiedDiagnostics.includes(d)).slice(0, 2) : [];
|
|
4031
|
+
if (session) session.notifiedDiagnostics = [.../* @__PURE__ */ new Set([
|
|
4032
|
+
...session.notifiedDiagnostics.filter((d) => report.diagnostics.includes(d)),
|
|
4033
|
+
...diagnostics
|
|
4034
|
+
])];
|
|
4035
|
+
const message2 = !session ? summarize(report) : [
|
|
4036
|
+
completionSummary(selected, report.reportPath),
|
|
4037
|
+
completionSummary(feedback, report.reportPath),
|
|
4038
|
+
...diagnostics.length ? [`Mason: verification limited. ${diagnostics.slice(0, 2).map(cleanText).join(" ")} Evidence: ${report.reportPath}.`] : []
|
|
4039
|
+
].filter(Boolean).join("\n") || null;
|
|
4040
|
+
const notify = message2 !== null;
|
|
4041
|
+
const persistReport = !state.latest || state.fingerprint !== inputs.fingerprint || notify || newSession || event.event === "task_end";
|
|
3679
4042
|
if (!persistReport) report.reportPath = state.latest;
|
|
3680
4043
|
state.updatedAt = now;
|
|
3681
4044
|
state.fingerprint = inputs.fingerprint;
|
|
3682
4045
|
state.latest = report.reportPath;
|
|
3683
4046
|
if (persistReport) await writeStoreJson(ws.root, report.reportPath, report);
|
|
3684
4047
|
await writeStoreJson(ws.root, ws.directory + "/state.json", state);
|
|
3685
|
-
return { report, message:
|
|
4048
|
+
return { report, message: message2 };
|
|
3686
4049
|
}));
|
|
3687
4050
|
}));
|
|
3688
4051
|
}
|
|
3689
4052
|
async function automationStatus(dir) {
|
|
4053
|
+
const { pendingKnowledge: pendingKnowledge2 } = await Promise.resolve().then(() => (init_pending(), pending_exports));
|
|
3690
4054
|
const ws = await profilePhase("automation.workspace", () => workspace(dir));
|
|
4055
|
+
const knowledge = await pendingKnowledge2(ws.root);
|
|
3691
4056
|
const execution = await executionStatus(ws.root, ws.directory);
|
|
3692
4057
|
const notifications = await failureNotifications(ws.root, ws.directory);
|
|
3693
4058
|
const raw = await readStoreJson(ws.root, ws.directory + "/state.json");
|
|
3694
|
-
if (raw === null) return { version: 1, status: execution.status === "not-observed" ? "not-observed" : "unavailable", root: ws.root, branch: ws.branch, baselinePaths: [], hosts: {}, execution, notifications };
|
|
4059
|
+
if (raw === null) return { version: 1, status: execution.status === "not-observed" ? "not-observed" : "unavailable", root: ws.root, branch: ws.branch, baselinePaths: [], hosts: {}, execution, notifications, knowledge };
|
|
3695
4060
|
const state = parseState(raw);
|
|
3696
4061
|
if (state.root !== ws.root || state.gitDir !== ws.gitDir || state.branch !== ws.branch) throw new Error("Automation state belongs to another workspace.");
|
|
3697
4062
|
const inputs = await readInputs(ws.root);
|
|
@@ -3714,6 +4079,8 @@ async function automationStatus(dir) {
|
|
|
3714
4079
|
hosts: hosts2,
|
|
3715
4080
|
execution,
|
|
3716
4081
|
notifications,
|
|
4082
|
+
knowledge,
|
|
4083
|
+
notificationBaselineHead: state.notifications?.baselineHead ?? null,
|
|
3717
4084
|
note: "Observed events do not prove all tool paths are intercepted. Run check to verify the retained evidence."
|
|
3718
4085
|
};
|
|
3719
4086
|
}
|
|
@@ -3721,6 +4088,7 @@ var SCOPE, priority, cleanText, analysisSchema, sharedSchema, baselineDigest;
|
|
|
3721
4088
|
var init_runtime = __esm({
|
|
3722
4089
|
"src/automation/runtime.ts"() {
|
|
3723
4090
|
"use strict";
|
|
4091
|
+
init_notifications();
|
|
3724
4092
|
init_profile();
|
|
3725
4093
|
init_inspection();
|
|
3726
4094
|
init_execution();
|
|
@@ -3732,34 +4100,34 @@ var init_runtime = __esm({
|
|
|
3732
4100
|
SCOPE = "Documentation audit evidence only. Hook receipts show observed events, not complete interception. Resolved findings no longer fail their checks or have a recorded assessment covering current evidence. Repair and review only within the user's task authorization.";
|
|
3733
4101
|
priority = { resolved: 0, "review-required": 1, unresolved: 2, unverified: 3 };
|
|
3734
4102
|
cleanText = (text2) => text2.replace(/[\u0000-\u001f\u007f-\u009f]/g, " ").slice(0, 250);
|
|
3735
|
-
analysisSchema =
|
|
3736
|
-
id:
|
|
3737
|
-
fingerprint:
|
|
3738
|
-
completedAt:
|
|
3739
|
-
baselineDigest:
|
|
4103
|
+
analysisSchema = z11.object({
|
|
4104
|
+
id: z11.string(),
|
|
4105
|
+
fingerprint: z11.string(),
|
|
4106
|
+
completedAt: z11.number(),
|
|
4107
|
+
baselineDigest: z11.string(),
|
|
3740
4108
|
currentAudit: reportSchema.nullable().optional(),
|
|
3741
|
-
report:
|
|
3742
|
-
version:
|
|
3743
|
-
status:
|
|
3744
|
-
root:
|
|
3745
|
-
branch:
|
|
3746
|
-
head:
|
|
3747
|
-
baselinePaths:
|
|
3748
|
-
reportPath:
|
|
3749
|
-
findings:
|
|
3750
|
-
diagnostics:
|
|
3751
|
-
checks:
|
|
3752
|
-
ran:
|
|
3753
|
-
reused:
|
|
3754
|
-
skipped:
|
|
3755
|
-
auditReused:
|
|
4109
|
+
report: z11.object({
|
|
4110
|
+
version: z11.literal(1),
|
|
4111
|
+
status: z11.enum(["verified", "issues-remain", "incomplete", "unavailable"]),
|
|
4112
|
+
root: z11.string(),
|
|
4113
|
+
branch: z11.string(),
|
|
4114
|
+
head: z11.string(),
|
|
4115
|
+
baselinePaths: z11.array(z11.string()),
|
|
4116
|
+
reportPath: z11.string(),
|
|
4117
|
+
findings: z11.array(repairFindingSchema),
|
|
4118
|
+
diagnostics: z11.array(z11.string()),
|
|
4119
|
+
checks: z11.object({
|
|
4120
|
+
ran: z11.array(z11.string()),
|
|
4121
|
+
reused: z11.array(z11.string()),
|
|
4122
|
+
skipped: z11.array(z11.object({ check: z11.string(), reason: z11.string(), doc: z11.string().optional() })),
|
|
4123
|
+
auditReused: z11.boolean().optional()
|
|
3756
4124
|
}),
|
|
3757
|
-
counts:
|
|
3758
|
-
capture:
|
|
3759
|
-
scope:
|
|
4125
|
+
counts: z11.object({ resolved: z11.number(), unresolved: z11.number(), "review-required": z11.number(), unverified: z11.number() }),
|
|
4126
|
+
capture: z11.enum(["observed", "unknown"]),
|
|
4127
|
+
scope: z11.string()
|
|
3760
4128
|
})
|
|
3761
4129
|
});
|
|
3762
|
-
sharedSchema =
|
|
4130
|
+
sharedSchema = z11.object({ analysis: analysisSchema, digest: z11.string() });
|
|
3763
4131
|
baselineDigest = async (ws, state) => profilePhase("automation.baselines", async () => hash(await Promise.all(
|
|
3764
4132
|
state.baselines.map(async (b) => [b.path, await readStoreJson(ws.root, b.path)])
|
|
3765
4133
|
)));
|
|
@@ -3768,9 +4136,9 @@ var init_runtime = __esm({
|
|
|
3768
4136
|
|
|
3769
4137
|
// src/automation/hook-root.ts
|
|
3770
4138
|
import fs15 from "fs/promises";
|
|
3771
|
-
import
|
|
4139
|
+
import path23 from "path";
|
|
3772
4140
|
async function repositoryRoot(dir) {
|
|
3773
|
-
if (!
|
|
4141
|
+
if (!path23.isAbsolute(dir)) throw new Error("Hook input working directory must be absolute.");
|
|
3774
4142
|
try {
|
|
3775
4143
|
return await fs15.realpath((await git3(dir, "rev-parse", "--show-toplevel")).trim());
|
|
3776
4144
|
} catch (error) {
|
|
@@ -3800,9 +4168,9 @@ var init_hook_root = __esm({
|
|
|
3800
4168
|
});
|
|
3801
4169
|
|
|
3802
4170
|
// src/setup/model.ts
|
|
3803
|
-
import { z as
|
|
4171
|
+
import { z as z12 } from "zod";
|
|
3804
4172
|
import fs16 from "fs/promises";
|
|
3805
|
-
import
|
|
4173
|
+
import path24 from "path";
|
|
3806
4174
|
async function loadSetup(root) {
|
|
3807
4175
|
const raw = await readStoreJson(root, SETUP_PATH);
|
|
3808
4176
|
if (raw === null) return null;
|
|
@@ -3811,7 +4179,7 @@ async function loadSetup(root) {
|
|
|
3811
4179
|
async function effectiveSetup(root) {
|
|
3812
4180
|
const local = await loadSetup(root);
|
|
3813
4181
|
if (local) return { root, setup: local };
|
|
3814
|
-
const common = await fs16.realpath(
|
|
4182
|
+
const common = await fs16.realpath(path24.resolve(root, (await git3(root, "rev-parse", "--git-common-dir")).trim()));
|
|
3815
4183
|
const own = await fs16.realpath((await git3(root, "rev-parse", "--absolute-git-dir")).trim());
|
|
3816
4184
|
if (common === own) return { root, setup: null };
|
|
3817
4185
|
const primary = (await git3(root, "worktree", "list", "--porcelain", "-z")).split("\0\0")[0].split("\0");
|
|
@@ -3824,7 +4192,7 @@ async function effectiveSetup(root) {
|
|
|
3824
4192
|
if (/must be run in a work tree/.test(String(error.stderr))) return { root, setup: null };
|
|
3825
4193
|
throw error;
|
|
3826
4194
|
}
|
|
3827
|
-
const inheritedCommon = await fs16.realpath(
|
|
4195
|
+
const inheritedCommon = await fs16.realpath(path24.resolve(
|
|
3828
4196
|
inheritedRoot,
|
|
3829
4197
|
(await git3(inheritedRoot, "rev-parse", "--git-common-dir")).trim()
|
|
3830
4198
|
));
|
|
@@ -3847,29 +4215,29 @@ var init_model = __esm({
|
|
|
3847
4215
|
"use strict";
|
|
3848
4216
|
init_evidence();
|
|
3849
4217
|
init_storage();
|
|
3850
|
-
setupHostSchema =
|
|
3851
|
-
configuredVersion:
|
|
3852
|
-
revision:
|
|
3853
|
-
fingerprint:
|
|
3854
|
-
mcpFingerprint:
|
|
3855
|
-
instructions:
|
|
4218
|
+
setupHostSchema = z12.object({
|
|
4219
|
+
configuredVersion: z12.string(),
|
|
4220
|
+
revision: z12.string().uuid(),
|
|
4221
|
+
fingerprint: z12.string(),
|
|
4222
|
+
mcpFingerprint: z12.string(),
|
|
4223
|
+
instructions: z12.array(z12.string())
|
|
3856
4224
|
});
|
|
3857
|
-
setupSchema =
|
|
4225
|
+
setupSchema = z12.object({ version: z12.literal(2), hosts: z12.object({
|
|
3858
4226
|
codex: setupHostSchema.optional(),
|
|
3859
4227
|
claude: setupHostSchema.optional()
|
|
3860
4228
|
}) });
|
|
3861
4229
|
SETUP_PATH = ".mason/local/setup.json";
|
|
3862
4230
|
INACTIVE_HOSTS_PATH = ".mason/local/inactive-hosts.json";
|
|
3863
|
-
inactiveHostsSchema =
|
|
3864
|
-
receiptSchema =
|
|
3865
|
-
version:
|
|
3866
|
-
host:
|
|
3867
|
-
status:
|
|
3868
|
-
initialReportPath:
|
|
3869
|
-
initialBaselinePaths:
|
|
3870
|
-
root:
|
|
3871
|
-
revision:
|
|
3872
|
-
configuredAt:
|
|
4231
|
+
inactiveHostsSchema = z12.object({ version: z12.literal(1), hosts: z12.array(z12.enum(["claude", "codex"])) });
|
|
4232
|
+
receiptSchema = z12.object({
|
|
4233
|
+
version: z12.literal(1),
|
|
4234
|
+
host: z12.enum(["codex", "claude"]),
|
|
4235
|
+
status: z12.enum(["installing", "configured"]),
|
|
4236
|
+
initialReportPath: z12.string(),
|
|
4237
|
+
initialBaselinePaths: z12.array(z12.string()),
|
|
4238
|
+
root: z12.string(),
|
|
4239
|
+
revision: z12.string().uuid(),
|
|
4240
|
+
configuredAt: z12.string().optional()
|
|
3873
4241
|
});
|
|
3874
4242
|
}
|
|
3875
4243
|
});
|
|
@@ -3930,7 +4298,7 @@ var init_launcher = __esm({
|
|
|
3930
4298
|
"use strict";
|
|
3931
4299
|
init_evidence();
|
|
3932
4300
|
init_model();
|
|
3933
|
-
executingVersion = () => true ? "0.19.
|
|
4301
|
+
executingVersion = () => true ? "0.19.2" : "development";
|
|
3934
4302
|
mcpCommand = (host) => globalCommand(["--setup-host", host, "mcp"]);
|
|
3935
4303
|
hookCommand = (host) => `mason --setup-host ${host} auto`;
|
|
3936
4304
|
}
|
|
@@ -3944,7 +4312,7 @@ __export(observations_exports, {
|
|
|
3944
4312
|
readObservation: () => readObservation
|
|
3945
4313
|
});
|
|
3946
4314
|
import fs18 from "fs/promises";
|
|
3947
|
-
import { z as
|
|
4315
|
+
import { z as z13 } from "zod";
|
|
3948
4316
|
function observationPath(directory, host, revision, version = executingVersion()) {
|
|
3949
4317
|
return `${directory}/activation/${host}-${revision}-${hash(version).slice(0, 12)}.json`;
|
|
3950
4318
|
}
|
|
@@ -4015,26 +4383,26 @@ var init_observations = __esm({
|
|
|
4015
4383
|
init_storage();
|
|
4016
4384
|
init_model();
|
|
4017
4385
|
init_launcher();
|
|
4018
|
-
observationSchema =
|
|
4019
|
-
version:
|
|
4020
|
-
masonVersion:
|
|
4021
|
-
root:
|
|
4022
|
-
revision:
|
|
4023
|
-
host:
|
|
4024
|
-
contextCalls:
|
|
4025
|
-
lastContextAt:
|
|
4026
|
-
sessions:
|
|
4027
|
-
events:
|
|
4028
|
-
at:
|
|
4029
|
-
verificationStatus:
|
|
4030
|
-
reportPath:
|
|
4386
|
+
observationSchema = z13.object({
|
|
4387
|
+
version: z13.literal(1),
|
|
4388
|
+
masonVersion: z13.string(),
|
|
4389
|
+
root: z13.string(),
|
|
4390
|
+
revision: z13.string(),
|
|
4391
|
+
host: z13.enum(["codex", "claude"]),
|
|
4392
|
+
contextCalls: z13.number().int().nonnegative(),
|
|
4393
|
+
lastContextAt: z13.string().optional(),
|
|
4394
|
+
sessions: z13.record(z13.object({
|
|
4395
|
+
events: z13.array(z13.enum(events)),
|
|
4396
|
+
at: z13.string(),
|
|
4397
|
+
verificationStatus: z13.string().optional(),
|
|
4398
|
+
reportPath: z13.string().optional()
|
|
4031
4399
|
}))
|
|
4032
4400
|
});
|
|
4033
4401
|
}
|
|
4034
4402
|
});
|
|
4035
4403
|
|
|
4036
4404
|
// src/automation/adapters.ts
|
|
4037
|
-
import { z as
|
|
4405
|
+
import { z as z14 } from "zod";
|
|
4038
4406
|
function normalizeHook(host, raw) {
|
|
4039
4407
|
hostSchema.parse(host);
|
|
4040
4408
|
const input = inputSchema.parse(raw);
|
|
@@ -4044,8 +4412,7 @@ function normalizeHook(host, raw) {
|
|
|
4044
4412
|
host,
|
|
4045
4413
|
sessionId: input.session_id,
|
|
4046
4414
|
toolId: input.tool_use_id,
|
|
4047
|
-
mutating: !!input.tool_name && !readOnly
|
|
4048
|
-
stopHookActive: input.stop_hook_active || input.permission_mode === "plan"
|
|
4415
|
+
mutating: !!input.tool_name && !readOnly
|
|
4049
4416
|
} };
|
|
4050
4417
|
}
|
|
4051
4418
|
async function runAutomationHook(host, stdin, options = {}) {
|
|
@@ -4083,7 +4450,7 @@ async function runAutomationHook(host, stdin, options = {}) {
|
|
|
4083
4450
|
if (warning) result.message = [result.message, warning].filter(Boolean).join("\n");
|
|
4084
4451
|
if (!result.message) return null;
|
|
4085
4452
|
if (name === "Stop") {
|
|
4086
|
-
return
|
|
4453
|
+
return { systemMessage: result.message };
|
|
4087
4454
|
}
|
|
4088
4455
|
return { hookSpecificOutput: { hookEventName: name, additionalContext: result.message } };
|
|
4089
4456
|
} catch (error) {
|
|
@@ -4117,15 +4484,15 @@ var init_adapters = __esm({
|
|
|
4117
4484
|
init_runtime();
|
|
4118
4485
|
init_store();
|
|
4119
4486
|
init_hook_root();
|
|
4120
|
-
inputSchema =
|
|
4121
|
-
cwd:
|
|
4122
|
-
session_id:
|
|
4123
|
-
hook_event_name:
|
|
4124
|
-
tool_name:
|
|
4125
|
-
tool_use_id:
|
|
4126
|
-
tool_input:
|
|
4127
|
-
stop_hook_active:
|
|
4128
|
-
permission_mode:
|
|
4487
|
+
inputSchema = z14.object({
|
|
4488
|
+
cwd: z14.string().min(1),
|
|
4489
|
+
session_id: z14.string().min(1).max(500),
|
|
4490
|
+
hook_event_name: z14.enum(["SessionStart", "UserPromptSubmit", "PreToolUse", "PostToolUse", "Stop"]),
|
|
4491
|
+
tool_name: z14.string().optional(),
|
|
4492
|
+
tool_use_id: z14.string().max(500).optional(),
|
|
4493
|
+
tool_input: z14.unknown().optional(),
|
|
4494
|
+
stop_hook_active: z14.boolean().optional(),
|
|
4495
|
+
permission_mode: z14.string().optional()
|
|
4129
4496
|
});
|
|
4130
4497
|
lifecycle = {
|
|
4131
4498
|
SessionStart: "session_start",
|
|
@@ -4146,7 +4513,7 @@ __export(files_exports, {
|
|
|
4146
4513
|
readText: () => readText
|
|
4147
4514
|
});
|
|
4148
4515
|
import fs19 from "fs/promises";
|
|
4149
|
-
import
|
|
4516
|
+
import path25 from "path";
|
|
4150
4517
|
import { randomUUID as randomUUID5 } from "crypto";
|
|
4151
4518
|
async function readText(root, file) {
|
|
4152
4519
|
try {
|
|
@@ -4179,7 +4546,7 @@ async function applyEdit(root, edit) {
|
|
|
4179
4546
|
return true;
|
|
4180
4547
|
}
|
|
4181
4548
|
const file = await storePath(root, edit.path, true);
|
|
4182
|
-
const temporary =
|
|
4549
|
+
const temporary = path25.join(path25.dirname(file), ".mason-setup-" + randomUUID5() + ".tmp");
|
|
4183
4550
|
try {
|
|
4184
4551
|
const mode = await fs19.stat(file).then((s) => s.mode & 511, () => 384);
|
|
4185
4552
|
const handle = await fs19.open(temporary, "wx", mode);
|
|
@@ -4214,7 +4581,7 @@ __export(ownership_exports, {
|
|
|
4214
4581
|
ownershipEdit: () => ownershipEdit,
|
|
4215
4582
|
ownershipSchema: () => ownershipSchema
|
|
4216
4583
|
});
|
|
4217
|
-
import { z as
|
|
4584
|
+
import { z as z15 } from "zod";
|
|
4218
4585
|
import { parse } from "smol-toml";
|
|
4219
4586
|
function blockSpan(text2, start, end) {
|
|
4220
4587
|
const starts = text2.split(start).length - 1, ends = text2.split(end).length - 1;
|
|
@@ -4265,30 +4632,30 @@ var init_ownership = __esm({
|
|
|
4265
4632
|
["<!-- mason:agents:start -->", "<!-- mason:agents:end -->"],
|
|
4266
4633
|
["# mason:mcp:start", "# mason:mcp:end"]
|
|
4267
4634
|
];
|
|
4268
|
-
fileSchema =
|
|
4269
|
-
created:
|
|
4270
|
-
initialHash:
|
|
4271
|
-
blocks:
|
|
4272
|
-
mcpHash:
|
|
4273
|
-
hookCommands:
|
|
4274
|
-
hooks:
|
|
4635
|
+
fileSchema = z15.object({
|
|
4636
|
+
created: z15.boolean(),
|
|
4637
|
+
initialHash: z15.string(),
|
|
4638
|
+
blocks: z15.record(z15.string()),
|
|
4639
|
+
mcpHash: z15.string().optional(),
|
|
4640
|
+
hookCommands: z15.array(z15.string()).optional(),
|
|
4641
|
+
hooks: z15.record(z15.array(z15.string())).optional()
|
|
4275
4642
|
});
|
|
4276
|
-
ownershipSchema =
|
|
4643
|
+
ownershipSchema = z15.object({ version: z15.literal(1), files: z15.record(fileSchema) });
|
|
4277
4644
|
}
|
|
4278
4645
|
});
|
|
4279
4646
|
|
|
4280
4647
|
// src/mcp/init.ts
|
|
4281
|
-
import { z as
|
|
4648
|
+
import { z as z16 } from "zod";
|
|
4282
4649
|
var markerSchema, CLAUDE_MD_SECTION, ASSISTANT_SETUP, QUICKSTART_PLAYBOOK, MAP_PLAYBOOK;
|
|
4283
4650
|
var init_init = __esm({
|
|
4284
4651
|
"src/mcp/init.ts"() {
|
|
4285
4652
|
"use strict";
|
|
4286
4653
|
init_storage();
|
|
4287
4654
|
init_files2();
|
|
4288
|
-
markerSchema =
|
|
4289
|
-
version:
|
|
4290
|
-
initializedAt:
|
|
4291
|
-
features:
|
|
4655
|
+
markerSchema = z16.object({
|
|
4656
|
+
version: z16.literal(1),
|
|
4657
|
+
initializedAt: z16.string(),
|
|
4658
|
+
features: z16.object({ confluence: z16.boolean().optional() }).passthrough().optional()
|
|
4292
4659
|
}).passthrough();
|
|
4293
4660
|
CLAUDE_MD_SECTION = `<!-- mason:start -->
|
|
4294
4661
|
## Mason project knowledge
|
|
@@ -4297,16 +4664,19 @@ Mason provides recorded decisions and file impact over MCP. A concept map is opt
|
|
|
4297
4664
|
|
|
4298
4665
|
- Task, bug, change request, or investigation of why the project works this way \u2192 \`get_context\` with the task text and known files: matching decisions, related tests, impact, and any available map entries.
|
|
4299
4666
|
- Before editing a file \u2192 \`get_impact\` to check references, tests, and historical change partners.
|
|
4300
|
-
- Investigation or correction reveals a reusable project lesson \u2192 \`save_decision\`
|
|
4667
|
+
- Investigation or correction reveals a reusable project lesson \u2192 \`save_decision\` as soon as the lesson is supported by evidence; do not wait for implementation, task completion, or merge. Record the established observation now and identify unimplemented remedies as tentative. Capture only the lasting constraint and reason, with anchors and known sources, owner, and recorder. This creates a proposal; never infer approval. Preserve uncertain causes and unknown attribution. Route project lessons here; keep personal preferences in agent memory. Skip code summaries, current checkout status, temporary task permissions, session trivia, and secrets. No qualifying lesson means no record.
|
|
4301
4668
|
- Before saving, compare the lesson with matching \`get_context\` records and any pending proposal. If new evidence changes the same lesson's assumptions, scope, or recommended action, revise it with \`save_decision(id: existingId)\`, including when it is accepted. Preserve supported rationale and sources, replace obsolete instructions, and add the new evidence. Create a separate record for a genuinely distinct lesson; skip unchanged restatements. Revision preserves history and leaves the accepted version operative until authorized review.
|
|
4302
4669
|
- Consult trust metadata before relying on entries: unknown or changed freshness requires inspection, stale or unknown map verification requires a fresh evidence review, and a recorded failed verdict means the description must be corrected. Check approval too: proposals are suggestions, legacy records are unreviewed, and accepted decisions are recorded constraints subject to freshness checks. An accepted revision remains operative while a pending proposal is reviewed; keep both versions and their freshness distinct.
|
|
4670
|
+
- Asked to review pending project knowledge \u2192 \`mason_automation(action: "status")\` lists pending proposals and uncommitted records. For ordinary tasks, inspect matching proposals through \`get_context\`; do not request acceptance merely because a proposal exists.
|
|
4303
4671
|
- Asked to review or re-verify a decision \u2192 \`review_decision\` first to inspect content, sources, history, and code changes. Record acceptance, reaffirmation, or retirement only when authorized by the user or a cited team review, with the actual reviewer and reason. Never infer approval from unchanged code. Review and commit the local record through the normal project workflow.
|
|
4304
4672
|
- For an architectural overview, use \`get_snapshot\` if a map is available. If \`map.status\` is missing or invalid, use available decisions and source evidence; do not start building a map unless requested.
|
|
4305
4673
|
- \`mason_init\` returns documentation audit and committed-diff review results, plus a short setup guide. Pass \`evidence\` with local CI manifest paths to include test and analysis results; the CLI equivalent is \`mason-review --evidence <manifest>\`. State skipped, unavailable, stale, or unknown checks explicitly. Related accepted decisions identify review context, not proven violations.
|
|
4306
4674
|
|
|
4307
|
-
- When documentation repair is authorized, use \`mason_repair(action: "prepare")\` before edits, keep its baselinePath, and use \`mason_repair(action: "verify", baselinePath)\` after edits and any final doc commit. Report every original finding's outcome and any new findings.
|
|
4675
|
+
- When documentation repair is authorized, use \`mason_repair(action: "prepare")\` before edits, keep its baselinePath, and use \`mason_repair(action: "verify", baselinePath)\` after edits and any final doc commit. Report every original finding's outcome and any new findings. Specific suppressed advisories remain unresolved; editing a doc does not approve it. Informational dependency notices require no review.
|
|
4308
4676
|
- When advisory assessment is authorized, use \`review_advisory(action: "prepare", baselinePath, findingId)\` to inspect original evidence and history. Record \`addressed\`, \`inapplicable\`, or \`deferred\` with the actual reviewer, reason in note, and returned reviewToken only after relevant edits are committed. Commit the assessment and verify the same baseline again. Relevant evidence changes reopen it; deferral stays outstanding. Decision findings use \`review_decision\`; advisory closure never approves a decision.
|
|
4309
4677
|
|
|
4678
|
+
- For a dependency advisory, \`review_advisory(action: "dismiss", baselinePath, findingId, reasonCode, note)\` records a one-step dismissal without reviewer or token. Use \`no-dependency-claims\` for a document with no dependency claims (reopens on document content changes), or \`unrelated-manifest-change\` for reviewed changes that do not affect its guidance (reopens on document or manifest changes). Keep the relevant scope committed; commit the dismissal to share it. This never approves a decision.
|
|
4679
|
+
|
|
4310
4680
|
- When Mason automation is installed, use \`mason_automation(action: "status")\` to inspect configured hooks and observed events, and \`mason_automation(action: "check")\` to resume its retained repair evidence. CLI fallback: \`mason-auto status\` / \`mason-auto check\`. Preserve existing baselines across sessions. Automatic checks do not authorize unrelated repairs or approve advisories.
|
|
4311
4681
|
|
|
4312
4682
|
Inspect source for what the retrieved context does not answer.
|
|
@@ -4321,7 +4691,7 @@ ${CLAUDE_MD_SECTION}`;
|
|
|
4321
4691
|
|
|
4322
4692
|
1. Explain the actionable findings with their source evidence. Separate audit issues, advisories, and skipped checks. The review covers committed changes from the merge base to HEAD; workingTree paths are not included in that review. An unavailable or empty check is not proof that the project is correct. Use the CLI for full output if a summary is truncated.
|
|
4323
4693
|
2. Address findings within the user's requested scope. Setup alone authorizes Mason host configuration, hooks, instructions, and ignore rules; rewriting existing project claims requires repair scope. If repair is authorized, call \`mason_repair(dir, action: "prepare")\` before editing; it saves the full original findings even when this summary is truncated. Inspect relevant source, make grounded edits, then call \`mason_repair(dir, action: "verify", baselinePath)\` with that same baseline, including after any final doc commit. Report resolved, unresolved, review-required, unverified, and new findings. Keep suppressed advisories visible even when setup has already dirtied a doc. Do not invent a decision just to populate the store.
|
|
4324
|
-
3. When the task reveals a real lesson or constraint, compare matching \`get_context\` records first. If its assumptions, scope, or recommended action changed, revise that record using \`save_decision(id: existingId)\`; create a separate record for a genuinely distinct lesson and skip unchanged restatements. Include title, body, category, anchors, and known owner/source/actor information. Preserve supported rationale and sources, replace obsolete instructions, and add new evidence. Missing attribution can be added later. The tool writes a local proposal; editing it preserves revision history and requires a new acceptance, while any earlier accepted revision remains operative. An unchanged save never refreshes its evidence. When decision review is requested, \`review_decision\` prepares the record and code evidence before any authorized verdict. Review and commit records through the normal workflow. Retrieve it on the next relevant task with \`get_context(dir, task, files)\`.
|
|
4694
|
+
3. Capture a supported lesson as soon as it is established; do not wait for implementation, task completion, or merge. Keep unimplemented remedies tentative. When the task reveals a real lesson or constraint, compare matching \`get_context\` records first. If its assumptions, scope, or recommended action changed, revise that record using \`save_decision(id: existingId)\`; create a separate record for a genuinely distinct lesson and skip unchanged restatements. Include title, body, category, anchors, and known owner/source/actor information. Preserve supported rationale and sources, replace obsolete instructions, and add new evidence. Missing attribution can be added later. The tool writes a local proposal; editing it preserves revision history and requires a new acceptance, while any earlier accepted revision remains operative. An unchanged save never refreshes its evidence. When decision review is requested, \`review_decision\` prepares the record and code evidence before any authorized verdict. Review and commit records through the normal workflow. Retrieve it on the next relevant task with \`get_context(dir, task, files)\`.
|
|
4325
4695
|
|
|
4326
4696
|
${ASSISTANT_SETUP}
|
|
4327
4697
|
|
|
@@ -4374,7 +4744,7 @@ __export(config_exports, {
|
|
|
4374
4744
|
});
|
|
4375
4745
|
import { isDeepStrictEqual } from "util";
|
|
4376
4746
|
import { parse as parse2, stringify } from "smol-toml";
|
|
4377
|
-
import { z as
|
|
4747
|
+
import { z as z17 } from "zod";
|
|
4378
4748
|
async function instructionEdits(root, host) {
|
|
4379
4749
|
const found = await Promise.all(DOC_CANDIDATES.map(async (file) => ({ path: file, text: await readText(root, file) })));
|
|
4380
4750
|
const existing = found.find((f) => f.text !== null);
|
|
@@ -4485,7 +4855,7 @@ var init_config = __esm({
|
|
|
4485
4855
|
init_install();
|
|
4486
4856
|
init_files2();
|
|
4487
4857
|
init_launcher();
|
|
4488
|
-
object =
|
|
4858
|
+
object = z17.record(z17.unknown());
|
|
4489
4859
|
mcpPath = (host) => host === "codex" ? ".codex/config.toml" : ".mcp.json";
|
|
4490
4860
|
TOML_START = "# mason:mcp:start";
|
|
4491
4861
|
TOML_END = "# mason:mcp:end";
|
|
@@ -4505,7 +4875,7 @@ __export(install_exports, {
|
|
|
4505
4875
|
planAutomationInstall: () => planAutomationInstall,
|
|
4506
4876
|
recordSchema: () => recordSchema2
|
|
4507
4877
|
});
|
|
4508
|
-
import { z as
|
|
4878
|
+
import { z as z18 } from "zod";
|
|
4509
4879
|
async function installationRecord(root) {
|
|
4510
4880
|
return recordSchema2.parse(await readStoreJson(root, AUTOMATION_PATH) ?? { version: 1, hosts: {} });
|
|
4511
4881
|
}
|
|
@@ -4586,9 +4956,9 @@ var init_install = __esm({
|
|
|
4586
4956
|
init_store();
|
|
4587
4957
|
init_files2();
|
|
4588
4958
|
init_ownership();
|
|
4589
|
-
groupSchema =
|
|
4590
|
-
automationConfigSchema =
|
|
4591
|
-
recordSchema2 =
|
|
4959
|
+
groupSchema = z18.object({ hooks: z18.array(z18.object({ type: z18.string(), command: z18.string().optional() }).passthrough()) }).passthrough();
|
|
4960
|
+
automationConfigSchema = z18.object({ hooks: z18.record(z18.array(groupSchema)).optional() }).passthrough();
|
|
4961
|
+
recordSchema2 = z18.object({ version: z18.literal(1), hosts: z18.record(z18.object({ command: z18.string() })) });
|
|
4592
4962
|
AUTOMATION_PATH = ".mason/local/automation.json";
|
|
4593
4963
|
configPath = (host) => host === "claude" ? ".claude/settings.json" : ".codex/hooks.json";
|
|
4594
4964
|
}
|
|
@@ -4602,10 +4972,10 @@ __export(teardown_exports, {
|
|
|
4602
4972
|
});
|
|
4603
4973
|
import { isDeepStrictEqual as isDeepStrictEqual2 } from "util";
|
|
4604
4974
|
import fs20 from "fs/promises";
|
|
4605
|
-
import
|
|
4975
|
+
import path26 from "path";
|
|
4606
4976
|
import { parse as parse3, stringify as stringify2 } from "smol-toml";
|
|
4607
|
-
import
|
|
4608
|
-
import { z as
|
|
4977
|
+
import fg8 from "fast-glob";
|
|
4978
|
+
import { z as z19 } from "zod";
|
|
4609
4979
|
async function planTeardown(root, selected) {
|
|
4610
4980
|
const edits = [], diagnostics = [];
|
|
4611
4981
|
const inputs = /* @__PURE__ */ new Map();
|
|
@@ -4625,7 +4995,7 @@ async function planTeardown(root, selected) {
|
|
|
4625
4995
|
const setupBefore = await read(SETUP_PATH);
|
|
4626
4996
|
const setup = setupBefore === null ? null : setupSchema.parse(JSON.parse(setupBefore));
|
|
4627
4997
|
const linked = (await workspace(root)).gitDir !== await fs20.realpath(
|
|
4628
|
-
|
|
4998
|
+
path26.resolve(root, (await git3(root, "rev-parse", "--git-common-dir")).trim())
|
|
4629
4999
|
);
|
|
4630
5000
|
const inactiveBefore = linked && !setup ? await read(INACTIVE_HOSTS_PATH) : null;
|
|
4631
5001
|
const inactive = inactiveBefore === null ? { version: 1, hosts: [] } : inactiveHostsSchema.parse(JSON.parse(inactiveBefore));
|
|
@@ -4748,7 +5118,7 @@ async function planTeardown(root, selected) {
|
|
|
4748
5118
|
if (diagnostics.length) edits.splice(instructionEditStart);
|
|
4749
5119
|
if (!diagnostics.length) {
|
|
4750
5120
|
const cleanup = [];
|
|
4751
|
-
const receiptFiles = await
|
|
5121
|
+
const receiptFiles = await fg8(
|
|
4752
5122
|
selected.map((host) => ".mason/reports/automation/*/setup-" + host + ".json"),
|
|
4753
5123
|
{ cwd: root, dot: true, onlyFiles: false, followSymbolicLinks: false }
|
|
4754
5124
|
);
|
|
@@ -4761,7 +5131,7 @@ async function planTeardown(root, selected) {
|
|
|
4761
5131
|
if (!remainingHost) await attempt(".mason/local/project.json", async () => {
|
|
4762
5132
|
const before = await read(".mason/local/project.json");
|
|
4763
5133
|
if (before === null) return;
|
|
4764
|
-
|
|
5134
|
+
z19.object({ version: z19.literal(1), initializedAt: z19.string() }).strict().parse(JSON.parse(before));
|
|
4765
5135
|
cleanup.push({ path: ".mason/local/project.json", before, after: null });
|
|
4766
5136
|
});
|
|
4767
5137
|
if (!diagnostics.length) {
|
|
@@ -4842,7 +5212,7 @@ var init_teardown = __esm({
|
|
|
4842
5212
|
init_model();
|
|
4843
5213
|
init_ownership();
|
|
4844
5214
|
hosts = ["codex", "claude"];
|
|
4845
|
-
object2 =
|
|
5215
|
+
object2 = z19.record(z19.unknown());
|
|
4846
5216
|
json = (value) => JSON.stringify(value, null, 2) + "\n";
|
|
4847
5217
|
}
|
|
4848
5218
|
});
|
|
@@ -4969,7 +5339,7 @@ var init_types2 = __esm({
|
|
|
4969
5339
|
});
|
|
4970
5340
|
|
|
4971
5341
|
// src/review/evidence/vitest.ts
|
|
4972
|
-
import { z as
|
|
5342
|
+
import { z as z20 } from "zod";
|
|
4973
5343
|
function parseVitest(raw, sourceRoot) {
|
|
4974
5344
|
const report = schema.parse(raw);
|
|
4975
5345
|
const findings = [], diagnostics = [];
|
|
@@ -5018,24 +5388,24 @@ var init_vitest = __esm({
|
|
|
5018
5388
|
"use strict";
|
|
5019
5389
|
init_paths2();
|
|
5020
5390
|
init_types2();
|
|
5021
|
-
count3 =
|
|
5022
|
-
schema =
|
|
5023
|
-
success:
|
|
5391
|
+
count3 = z20.number().int().nonnegative();
|
|
5392
|
+
schema = z20.object({
|
|
5393
|
+
success: z20.boolean(),
|
|
5024
5394
|
numTotalTests: count3,
|
|
5025
5395
|
numPassedTests: count3,
|
|
5026
5396
|
numFailedTests: count3,
|
|
5027
5397
|
numPendingTests: count3,
|
|
5028
5398
|
numTodoTests: count3,
|
|
5029
5399
|
numFailedTestSuites: count3,
|
|
5030
|
-
testResults:
|
|
5031
|
-
name:
|
|
5032
|
-
status:
|
|
5033
|
-
message:
|
|
5034
|
-
assertionResults:
|
|
5035
|
-
fullName:
|
|
5036
|
-
status:
|
|
5037
|
-
failureMessages:
|
|
5038
|
-
location:
|
|
5400
|
+
testResults: z20.array(z20.object({
|
|
5401
|
+
name: z20.string().min(1),
|
|
5402
|
+
status: z20.enum(["passed", "failed"]),
|
|
5403
|
+
message: z20.string().optional(),
|
|
5404
|
+
assertionResults: z20.array(z20.object({
|
|
5405
|
+
fullName: z20.string(),
|
|
5406
|
+
status: z20.enum(["passed", "failed", "pending", "skipped", "todo", "disabled"]),
|
|
5407
|
+
failureMessages: z20.array(z20.string()).nullable().optional(),
|
|
5408
|
+
location: z20.object({ line: count3, column: count3 }).nullable().optional()
|
|
5039
5409
|
}))
|
|
5040
5410
|
}))
|
|
5041
5411
|
});
|
|
@@ -5043,7 +5413,7 @@ var init_vitest = __esm({
|
|
|
5043
5413
|
});
|
|
5044
5414
|
|
|
5045
5415
|
// src/review/evidence/sarif.ts
|
|
5046
|
-
import { z as
|
|
5416
|
+
import { z as z21 } from "zod";
|
|
5047
5417
|
function parseSarif(raw, sourceRoot) {
|
|
5048
5418
|
const report = schema2.parse(raw), findings = [], diagnostics = [];
|
|
5049
5419
|
let active = 0, suppressed = 0, absent = 0, unresolved = 0;
|
|
@@ -5134,29 +5504,29 @@ var init_sarif = __esm({
|
|
|
5134
5504
|
"use strict";
|
|
5135
5505
|
init_paths2();
|
|
5136
5506
|
init_types2();
|
|
5137
|
-
index =
|
|
5138
|
-
artifact =
|
|
5139
|
-
message =
|
|
5140
|
-
level =
|
|
5141
|
-
location =
|
|
5142
|
-
schema2 =
|
|
5143
|
-
version:
|
|
5144
|
-
runs:
|
|
5145
|
-
tool:
|
|
5146
|
-
invocations:
|
|
5147
|
-
versionControlProvenance:
|
|
5148
|
-
originalUriBaseIds:
|
|
5149
|
-
artifacts:
|
|
5150
|
-
results:
|
|
5151
|
-
ruleId:
|
|
5507
|
+
index = z21.number().int().nonnegative();
|
|
5508
|
+
artifact = z21.object({ uri: z21.string().optional(), uriBaseId: z21.string().optional(), index: index.optional() });
|
|
5509
|
+
message = z21.object({ text: z21.string().optional(), markdown: z21.string().optional(), id: z21.string().optional(), arguments: z21.array(z21.string()).optional() });
|
|
5510
|
+
level = z21.enum(["error", "warning", "note", "none"]);
|
|
5511
|
+
location = z21.object({ physicalLocation: z21.object({ artifactLocation: artifact.optional(), region: z21.object({ startLine: index.optional(), startColumn: index.optional() }).optional() }).optional() });
|
|
5512
|
+
schema2 = z21.object({
|
|
5513
|
+
version: z21.literal("2.1.0"),
|
|
5514
|
+
runs: z21.array(z21.object({
|
|
5515
|
+
tool: z21.object({ driver: z21.object({ name: z21.string().min(1), rules: z21.array(z21.object({ id: z21.string(), defaultConfiguration: z21.object({ level: level.optional() }).optional(), messageStrings: z21.record(message).optional() })).optional(), globalMessageStrings: z21.record(message).optional() }) }),
|
|
5516
|
+
invocations: z21.array(z21.object({ executionSuccessful: z21.boolean(), commandLine: z21.string().optional(), toolExecutionNotifications: z21.array(z21.object({ level: level.optional(), message })).optional() })).optional(),
|
|
5517
|
+
versionControlProvenance: z21.array(z21.object({ revisionId: z21.string().optional() })).optional(),
|
|
5518
|
+
originalUriBaseIds: z21.record(artifact).optional(),
|
|
5519
|
+
artifacts: z21.array(z21.object({ location: artifact.optional() })).optional(),
|
|
5520
|
+
results: z21.array(z21.object({
|
|
5521
|
+
ruleId: z21.string().optional(),
|
|
5152
5522
|
ruleIndex: index.optional(),
|
|
5153
5523
|
message,
|
|
5154
5524
|
level: level.optional(),
|
|
5155
|
-
kind:
|
|
5156
|
-
baselineState:
|
|
5157
|
-
suppressions:
|
|
5158
|
-
locations:
|
|
5159
|
-
relatedLocations:
|
|
5525
|
+
kind: z21.enum(["fail", "pass", "open", "informational", "notApplicable", "review"]).optional(),
|
|
5526
|
+
baselineState: z21.enum(["new", "unchanged", "updated", "absent"]).optional(),
|
|
5527
|
+
suppressions: z21.array(z21.object({ kind: z21.enum(["inSource", "external"]), status: z21.enum(["accepted", "underReview", "rejected"]).optional() })).nullable().optional(),
|
|
5528
|
+
locations: z21.array(location).optional(),
|
|
5529
|
+
relatedLocations: z21.array(location).optional()
|
|
5160
5530
|
})).optional()
|
|
5161
5531
|
}))
|
|
5162
5532
|
});
|
|
@@ -5164,7 +5534,7 @@ var init_sarif = __esm({
|
|
|
5164
5534
|
});
|
|
5165
5535
|
|
|
5166
5536
|
// src/review/evidence/check.ts
|
|
5167
|
-
import { z as
|
|
5537
|
+
import { z as z22 } from "zod";
|
|
5168
5538
|
function parseCheck(raw, sourceRoot) {
|
|
5169
5539
|
const report = schema3.parse(raw);
|
|
5170
5540
|
const diagnostics = [], findings = [];
|
|
@@ -5204,17 +5574,17 @@ var init_check = __esm({
|
|
|
5204
5574
|
"use strict";
|
|
5205
5575
|
init_paths2();
|
|
5206
5576
|
init_types2();
|
|
5207
|
-
schema3 =
|
|
5208
|
-
version:
|
|
5209
|
-
commit:
|
|
5210
|
-
results:
|
|
5211
|
-
name:
|
|
5212
|
-
status:
|
|
5213
|
-
message:
|
|
5214
|
-
locations:
|
|
5215
|
-
file:
|
|
5216
|
-
line:
|
|
5217
|
-
column:
|
|
5577
|
+
schema3 = z22.object({
|
|
5578
|
+
version: z22.literal(1),
|
|
5579
|
+
commit: z22.string().regex(/^(?:[a-fA-F0-9]{40}|[a-fA-F0-9]{64})$/).optional(),
|
|
5580
|
+
results: z22.array(z22.object({
|
|
5581
|
+
name: z22.string().min(1).max(1e3),
|
|
5582
|
+
status: z22.enum(["passed", "failed", "skipped"]),
|
|
5583
|
+
message: z22.string().max(1e5).optional(),
|
|
5584
|
+
locations: z22.array(z22.object({
|
|
5585
|
+
file: z22.string().min(1).max(4e3),
|
|
5586
|
+
line: z22.number().int().positive().optional(),
|
|
5587
|
+
column: z22.number().int().positive().optional()
|
|
5218
5588
|
})).max(100).default([])
|
|
5219
5589
|
})).max(1e5)
|
|
5220
5590
|
});
|
|
@@ -5222,10 +5592,10 @@ var init_check = __esm({
|
|
|
5222
5592
|
});
|
|
5223
5593
|
|
|
5224
5594
|
// src/review/evidence.ts
|
|
5225
|
-
import
|
|
5226
|
-
import { z as
|
|
5595
|
+
import path27 from "path";
|
|
5596
|
+
import { z as z23 } from "zod";
|
|
5227
5597
|
function relativeArtifact(root, file) {
|
|
5228
|
-
const relative =
|
|
5598
|
+
const relative = path27.isAbsolute(file) ? path27.relative(root, file) : file;
|
|
5229
5599
|
const normalized = normalizeRepoPath(relative);
|
|
5230
5600
|
if (!normalized) throw new Error(`Evidence artifact must be inside the repository: ${file}`);
|
|
5231
5601
|
return normalized;
|
|
@@ -5264,7 +5634,7 @@ async function collectReviewEvidence(root, manifests, changedFiles, decisions, d
|
|
|
5264
5634
|
let manifest, raw;
|
|
5265
5635
|
try {
|
|
5266
5636
|
manifest = relativeArtifact(root, supplied);
|
|
5267
|
-
raw =
|
|
5637
|
+
raw = z23.object({ version: z23.literal(1), checks: z23.array(z23.unknown()) }).parse(await readStoreJson(root, manifest));
|
|
5268
5638
|
} catch (error) {
|
|
5269
5639
|
output.diagnostics.push(`${supplied}: ${String(error)}`);
|
|
5270
5640
|
continue;
|
|
@@ -5322,7 +5692,7 @@ async function collectReviewEvidence(root, manifests, changedFiles, decisions, d
|
|
|
5322
5692
|
const report = await readStoreJson(root, file);
|
|
5323
5693
|
if (report === null) throw new Error(`Report artifact is missing: ${file}`);
|
|
5324
5694
|
const sourceRoot = check.sourceRoot ?? root;
|
|
5325
|
-
if (!
|
|
5695
|
+
if (!path27.posix.isAbsolute(sourceRoot) && !/^[A-Za-z]:[\\/]/.test(sourceRoot)) throw new Error("sourceRoot must identify the absolute checkout root on the check runner.");
|
|
5326
5696
|
const parsed = check.report.format === "vitest-json" ? parseVitest(report, sourceRoot) : check.report.format === "mason-check-json" ? parseCheck(report, sourceRoot) : parseSarif(report, sourceRoot);
|
|
5327
5697
|
result.outcome = parsed.outcome;
|
|
5328
5698
|
result.counts = parsed.counts;
|
|
@@ -5419,32 +5789,32 @@ var init_evidence2 = __esm({
|
|
|
5419
5789
|
init_sarif();
|
|
5420
5790
|
init_check();
|
|
5421
5791
|
init_types2();
|
|
5422
|
-
checkSchema2 =
|
|
5423
|
-
id:
|
|
5424
|
-
kind:
|
|
5425
|
-
tool:
|
|
5426
|
-
command:
|
|
5427
|
-
commit:
|
|
5428
|
-
workingTreeClean:
|
|
5429
|
-
source:
|
|
5430
|
-
sourceRoot:
|
|
5431
|
-
status:
|
|
5432
|
-
reason:
|
|
5433
|
-
exitCode:
|
|
5434
|
-
environment:
|
|
5435
|
-
toolVersion:
|
|
5436
|
-
runtime:
|
|
5437
|
-
platform:
|
|
5438
|
-
configuration:
|
|
5792
|
+
checkSchema2 = z23.object({
|
|
5793
|
+
id: z23.string().min(1).max(100),
|
|
5794
|
+
kind: z23.enum(["tests", "static-analysis", "security", "complexity", "duplication", "documentation"]),
|
|
5795
|
+
tool: z23.string().min(1).max(200),
|
|
5796
|
+
command: z23.string().min(1).max(2e3),
|
|
5797
|
+
commit: z23.string().regex(/^(?:[a-fA-F0-9]{40}|[a-fA-F0-9]{64})$/).nullable().optional(),
|
|
5798
|
+
workingTreeClean: z23.boolean().optional(),
|
|
5799
|
+
source: z23.string().max(2e3).optional(),
|
|
5800
|
+
sourceRoot: z23.string().min(1).max(4e3).optional(),
|
|
5801
|
+
status: z23.enum(["completed", "skipped", "unavailable"]).default("completed"),
|
|
5802
|
+
reason: z23.string().min(1).max(2e3).optional(),
|
|
5803
|
+
exitCode: z23.number().int().nullable().optional(),
|
|
5804
|
+
environment: z23.object({
|
|
5805
|
+
toolVersion: z23.string().min(1).max(200),
|
|
5806
|
+
runtime: z23.string().min(1).max(200).optional(),
|
|
5807
|
+
platform: z23.string().min(1).max(200).optional(),
|
|
5808
|
+
configuration: z23.array(z23.string().min(1).max(4e3)).max(100).optional()
|
|
5439
5809
|
}).optional(),
|
|
5440
|
-
report:
|
|
5810
|
+
report: z23.object({ format: z23.enum(["vitest-json", "sarif", "mason-check-json"]), path: z23.string().min(1).max(4e3) }).optional()
|
|
5441
5811
|
});
|
|
5442
5812
|
}
|
|
5443
5813
|
});
|
|
5444
5814
|
|
|
5445
5815
|
// src/review/review.ts
|
|
5446
5816
|
import fs21 from "fs/promises";
|
|
5447
|
-
import
|
|
5817
|
+
import path28 from "path";
|
|
5448
5818
|
import { execFile as execFile4 } from "child_process";
|
|
5449
5819
|
import { promisify as promisify4 } from "util";
|
|
5450
5820
|
async function resolveMergeBase(resolvedRoot, base, head) {
|
|
@@ -5473,7 +5843,7 @@ function anchorsTouched(record, changedFiles) {
|
|
|
5473
5843
|
return matchingPaths(record.files, changedFiles);
|
|
5474
5844
|
}
|
|
5475
5845
|
async function computeReview(rootDir, base, options = {}) {
|
|
5476
|
-
const resolvedRoot =
|
|
5846
|
+
const resolvedRoot = path28.resolve(rootDir);
|
|
5477
5847
|
const head = await getCurrentGitHash(resolvedRoot);
|
|
5478
5848
|
if (head === "unknown") return null;
|
|
5479
5849
|
const mergeBase = await resolveMergeBase(resolvedRoot, base, head);
|
|
@@ -5523,7 +5893,7 @@ async function computeReview(rootDir, base, options = {}) {
|
|
|
5523
5893
|
analyzed,
|
|
5524
5894
|
async (relPath) => {
|
|
5525
5895
|
try {
|
|
5526
|
-
await fs21.access(
|
|
5896
|
+
await fs21.access(path28.join(resolvedRoot, relPath));
|
|
5527
5897
|
return true;
|
|
5528
5898
|
} catch {
|
|
5529
5899
|
return false;
|
|
@@ -5909,16 +6279,16 @@ var init_setup = __esm({
|
|
|
5909
6279
|
// src/distribution/path.ts
|
|
5910
6280
|
import fs22 from "fs/promises";
|
|
5911
6281
|
import os3 from "os";
|
|
5912
|
-
import
|
|
6282
|
+
import path29 from "path";
|
|
5913
6283
|
import { execFile as execFile5 } from "child_process";
|
|
5914
6284
|
import { promisify as promisify5 } from "util";
|
|
5915
6285
|
import { createHash as createHash4, randomUUID as randomUUID7 } from "crypto";
|
|
5916
|
-
import { z as
|
|
6286
|
+
import { z as z24 } from "zod";
|
|
5917
6287
|
async function configurePath(bin, previous, save, options = {}) {
|
|
5918
6288
|
const env = options.env ?? process.env, platform = options.platform ?? process.platform;
|
|
5919
6289
|
const changes = [...previous];
|
|
5920
6290
|
const currentPath = platform === "win32" ? Object.entries(env).find(([key]) => key.toLowerCase() === "path")?.[1] : env.PATH;
|
|
5921
|
-
const alreadyOnPath = platform === "win32" ? hasWindowsEntry(currentPath ?? null, bin, env) : (currentPath ?? "").split(":").some((entry) =>
|
|
6291
|
+
const alreadyOnPath = platform === "win32" ? hasWindowsEntry(currentPath ?? null, bin, env) : (currentPath ?? "").split(":").some((entry) => path29.posix.isAbsolute(entry) && path29.posix.resolve(entry) === path29.posix.resolve(bin));
|
|
5922
6292
|
const setupHint = "To connect a project, run one of these inside its directory:\n Codex: mason setup --host codex\n Claude Code: mason setup --host claude";
|
|
5923
6293
|
const message2 = (alreadyOnPath ? "" : "Open a new terminal to use mason.\n") + setupHint;
|
|
5924
6294
|
const manual = (reason2) => ({ status: "manual", changes, message: `${reason2}
|
|
@@ -5938,15 +6308,15 @@ ${alreadyOnPath ? "" : `Add ${bin} to your PATH.
|
|
|
5938
6308
|
}
|
|
5939
6309
|
return { status: "configured", changes, message: message2 };
|
|
5940
6310
|
}
|
|
5941
|
-
const shell =
|
|
6311
|
+
const shell = path29.basename(env.SHELL ?? os3.userInfo().shell ?? "");
|
|
5942
6312
|
const userHome = options.userHome ?? os3.homedir();
|
|
5943
6313
|
let profiles2;
|
|
5944
6314
|
if (!["bash", "zsh", "sh"].includes(shell)) return manual(`Automatic PATH setup is unavailable for ${shell || "an unknown shell"}.`);
|
|
5945
|
-
if (env.MASON_PROFILE) profiles2 = [
|
|
5946
|
-
else if (shell === "zsh") profiles2 = [
|
|
5947
|
-
else if (shell === "sh") profiles2 = [
|
|
6315
|
+
if (env.MASON_PROFILE) profiles2 = [path29.resolve(env.MASON_PROFILE)];
|
|
6316
|
+
else if (shell === "zsh") profiles2 = [path29.join(env.ZDOTDIR || userHome, ".zshrc")];
|
|
6317
|
+
else if (shell === "sh") profiles2 = [path29.join(userHome, ".profile")];
|
|
5948
6318
|
else {
|
|
5949
|
-
const candidates = [".bash_profile", ".bash_login", ".profile"].map((name) =>
|
|
6319
|
+
const candidates = [".bash_profile", ".bash_login", ".profile"].map((name) => path29.join(userHome, name));
|
|
5950
6320
|
let login = candidates[2];
|
|
5951
6321
|
for (const file of candidates) if (await fs22.lstat(file).then(() => true, (error) => {
|
|
5952
6322
|
if (error.code === "ENOENT") return false;
|
|
@@ -5955,7 +6325,7 @@ ${alreadyOnPath ? "" : `Add ${bin} to your PATH.
|
|
|
5955
6325
|
login = file;
|
|
5956
6326
|
break;
|
|
5957
6327
|
}
|
|
5958
|
-
profiles2 = [
|
|
6328
|
+
profiles2 = [path29.join(userHome, ".bashrc"), login];
|
|
5959
6329
|
}
|
|
5960
6330
|
const marker = `# >>> mason PATH ${createHash4("sha256").update(bin).digest("hex").slice(0, 12)} >>>`;
|
|
5961
6331
|
for (const profile of profiles2) {
|
|
@@ -5963,7 +6333,7 @@ ${alreadyOnPath ? "" : `Add ${bin} to your PATH.
|
|
|
5963
6333
|
if (error.code === "ENOENT") return null;
|
|
5964
6334
|
throw error;
|
|
5965
6335
|
});
|
|
5966
|
-
const file = stat?.isSymbolicLink() ? await fs22.realpath(profile) :
|
|
6336
|
+
const file = stat?.isSymbolicLink() ? await fs22.realpath(profile) : path29.resolve(profile);
|
|
5967
6337
|
const original = await fs22.readFile(file).catch((error) => {
|
|
5968
6338
|
if (error.code === "ENOENT" && !stat) return null;
|
|
5969
6339
|
throw error;
|
|
@@ -5979,7 +6349,7 @@ ${alreadyOnPath ? "" : `Add ${bin} to your PATH.
|
|
|
5979
6349
|
changes.push({ kind: "profile", file, block, created: original === null });
|
|
5980
6350
|
await save(changes);
|
|
5981
6351
|
}
|
|
5982
|
-
await fs22.mkdir(
|
|
6352
|
+
await fs22.mkdir(path29.dirname(file), { recursive: true });
|
|
5983
6353
|
const handle = await fs22.open(file, original === null ? "ax+" : "a+", 384);
|
|
5984
6354
|
try {
|
|
5985
6355
|
if (!(await handle.readFile()).equals(original ?? Buffer.alloc(0))) throw new Error(`Shell profile changed during installation: ${profile}`);
|
|
@@ -6075,12 +6445,12 @@ var init_path = __esm({
|
|
|
6075
6445
|
"src/distribution/path.ts"() {
|
|
6076
6446
|
"use strict";
|
|
6077
6447
|
execute2 = promisify5(execFile5);
|
|
6078
|
-
pathChangeSchema =
|
|
6079
|
-
|
|
6080
|
-
|
|
6448
|
+
pathChangeSchema = z24.discriminatedUnion("kind", [
|
|
6449
|
+
z24.object({ kind: z24.literal("profile"), file: z24.string(), block: z24.string(), created: z24.boolean() }),
|
|
6450
|
+
z24.object({ kind: z24.literal("windows"), entry: z24.string(), created: z24.boolean() })
|
|
6081
6451
|
]);
|
|
6082
6452
|
quote = (s) => "'" + s.replace(/'/g, "'\\''") + "'";
|
|
6083
|
-
normalizeWindowsEntry = (value) =>
|
|
6453
|
+
normalizeWindowsEntry = (value) => path29.win32.normalize(value.trim().replace(/^"(.*)"$/, "$1")).replace(/[\\/]+$/, "").toLowerCase();
|
|
6084
6454
|
hasWindowsEntry = (value, entry, env = process.env) => {
|
|
6085
6455
|
const variables = new Map(Object.entries(env).map(([key, value2]) => [key.toLowerCase(), value2]));
|
|
6086
6456
|
const expand = (part) => part.replace(/%([^%]+)%/g, (match, key) => variables.get(key.toLowerCase()) ?? match);
|
|
@@ -6103,10 +6473,10 @@ __export(state_exports, {
|
|
|
6103
6473
|
updatePolicySchema: () => updatePolicySchema
|
|
6104
6474
|
});
|
|
6105
6475
|
import os4 from "os";
|
|
6106
|
-
import
|
|
6476
|
+
import path30 from "path";
|
|
6107
6477
|
import { fileURLToPath } from "url";
|
|
6108
6478
|
import fs23 from "fs/promises";
|
|
6109
|
-
import { z as
|
|
6479
|
+
import { z as z25 } from "zod";
|
|
6110
6480
|
async function readInstallation(home) {
|
|
6111
6481
|
const raw = await readStoreJson(home, "install.json");
|
|
6112
6482
|
if (!raw) throw new Error("Run this command from a standalone installation. npm users can upgrade with npm.");
|
|
@@ -6115,9 +6485,9 @@ async function readInstallation(home) {
|
|
|
6115
6485
|
return record;
|
|
6116
6486
|
}
|
|
6117
6487
|
function standaloneLocation() {
|
|
6118
|
-
const bundle =
|
|
6119
|
-
const home =
|
|
6120
|
-
if (
|
|
6488
|
+
const bundle = path30.resolve(path30.dirname(fileURLToPath(import.meta.url)), "../..");
|
|
6489
|
+
const home = path30.dirname(path30.dirname(bundle));
|
|
6490
|
+
if (path30.dirname(bundle) !== path30.join(home, "versions") || !/^[a-f0-9]{24}$/.test(path30.basename(bundle))) {
|
|
6121
6491
|
return null;
|
|
6122
6492
|
}
|
|
6123
6493
|
return { home, bundle };
|
|
@@ -6146,42 +6516,42 @@ var init_state = __esm({
|
|
|
6146
6516
|
"use strict";
|
|
6147
6517
|
init_storage();
|
|
6148
6518
|
init_path();
|
|
6149
|
-
releaseVersion =
|
|
6150
|
-
bundleId =
|
|
6151
|
-
updatePolicySchema =
|
|
6152
|
-
enabled:
|
|
6153
|
-
revision:
|
|
6519
|
+
releaseVersion = z25.string().regex(/^\d+\.\d+\.\d+(?:-[A-Za-z0-9.-]+)?$/);
|
|
6520
|
+
bundleId = z25.string().regex(/^[a-f0-9]{24}$/);
|
|
6521
|
+
updatePolicySchema = z25.object({
|
|
6522
|
+
enabled: z25.boolean(),
|
|
6523
|
+
revision: z25.string(),
|
|
6154
6524
|
pinnedVersion: releaseVersion.optional(),
|
|
6155
|
-
mirror:
|
|
6525
|
+
mirror: z25.string().optional()
|
|
6156
6526
|
});
|
|
6157
|
-
recordSchema3 =
|
|
6158
|
-
format:
|
|
6159
|
-
home:
|
|
6160
|
-
bin:
|
|
6527
|
+
recordSchema3 = z25.object({
|
|
6528
|
+
format: z25.literal(1),
|
|
6529
|
+
home: z25.string(),
|
|
6530
|
+
bin: z25.string(),
|
|
6161
6531
|
current: bundleId,
|
|
6162
6532
|
version: releaseVersion,
|
|
6163
|
-
launchers:
|
|
6164
|
-
previousLaunchers:
|
|
6165
|
-
pathChanges:
|
|
6533
|
+
launchers: z25.record(z25.string()),
|
|
6534
|
+
previousLaunchers: z25.record(z25.string()).optional(),
|
|
6535
|
+
pathChanges: z25.array(pathChangeSchema).optional(),
|
|
6166
6536
|
updates: updatePolicySchema.optional(),
|
|
6167
|
-
previous:
|
|
6168
|
-
pending:
|
|
6537
|
+
previous: z25.object({ id: bundleId, version: releaseVersion }).optional(),
|
|
6538
|
+
pending: z25.object({ id: bundleId, version: releaseVersion, policyRevision: z25.string() }).optional()
|
|
6169
6539
|
});
|
|
6170
|
-
defaultHome = () => process.env.MASON_HOME ?? (process.platform === "win32" ?
|
|
6540
|
+
defaultHome = () => process.env.MASON_HOME ?? (process.platform === "win32" ? path30.join(process.env.LOCALAPPDATA ?? os4.homedir(), "Mason") : path30.join(os4.homedir(), ".local/share/mason"));
|
|
6171
6541
|
}
|
|
6172
6542
|
});
|
|
6173
6543
|
|
|
6174
6544
|
// src/distribution/bundle.ts
|
|
6175
6545
|
import fs24 from "fs/promises";
|
|
6176
|
-
import
|
|
6546
|
+
import path31 from "path";
|
|
6177
6547
|
import { createHash as createHash5 } from "crypto";
|
|
6178
|
-
import { z as
|
|
6548
|
+
import { z as z26 } from "zod";
|
|
6179
6549
|
async function readBundle(root) {
|
|
6180
6550
|
const bytes = await fs24.readFile(await storePath(root, "bundle.json"));
|
|
6181
6551
|
const manifest = bundleSchema.parse(JSON.parse(bytes.toString("utf8")));
|
|
6182
6552
|
const windows = manifest.target.startsWith("win32-");
|
|
6183
6553
|
for (const file of Object.keys(manifest.files)) {
|
|
6184
|
-
if (file.includes("\\") || file.split("/").some((p) => !p || p === "." || p === "..") ||
|
|
6554
|
+
if (file.includes("\\") || file.split("/").some((p) => !p || p === "." || p === "..") || path31.isAbsolute(file)) {
|
|
6185
6555
|
throw new Error("Unsafe path in Mason bundle: " + file);
|
|
6186
6556
|
}
|
|
6187
6557
|
}
|
|
@@ -6206,25 +6576,41 @@ var init_bundle = __esm({
|
|
|
6206
6576
|
"use strict";
|
|
6207
6577
|
init_storage();
|
|
6208
6578
|
targets = ["darwin-arm64", "darwin-x64", "linux-arm64", "linux-x64", "win32-arm64", "win32-x64"];
|
|
6209
|
-
digest2 =
|
|
6210
|
-
bundleSchema =
|
|
6211
|
-
format:
|
|
6212
|
-
version:
|
|
6213
|
-
target:
|
|
6214
|
-
nodeVersion:
|
|
6215
|
-
files:
|
|
6579
|
+
digest2 = z26.string().regex(/^[a-f0-9]{64}$/);
|
|
6580
|
+
bundleSchema = z26.object({
|
|
6581
|
+
format: z26.literal(1),
|
|
6582
|
+
version: z26.string().regex(/^\d+\.\d+\.\d+(?:-[A-Za-z0-9.-]+)?$/),
|
|
6583
|
+
target: z26.enum(targets),
|
|
6584
|
+
nodeVersion: z26.string(),
|
|
6585
|
+
files: z26.record(digest2)
|
|
6216
6586
|
}).strict();
|
|
6217
6587
|
sha256 = (bytes) => createHash5("sha256").update(bytes).digest("hex");
|
|
6218
6588
|
}
|
|
6219
6589
|
});
|
|
6220
6590
|
|
|
6591
|
+
// src/distribution/trust.ts
|
|
6592
|
+
import tls from "tls";
|
|
6593
|
+
function releaseRequestError(error) {
|
|
6594
|
+
const cause = error instanceof Error ? error.cause : void 0;
|
|
6595
|
+
const code = cause && typeof cause === "object" && "code" in cause ? String(cause.code) : void 0;
|
|
6596
|
+
const detail = code ?? (error instanceof Error ? error.message : String(error));
|
|
6597
|
+
const certificateError = code && /CERT|SELF_SIGNED|UNABLE_TO_VERIFY_LEAF_SIGNATURE/.test(code);
|
|
6598
|
+
const hint = certificateError ? " Check that your company's CA certificate is trusted by your operating system, or provide its PEM file with NODE_EXTRA_CA_CERTS." : " Check network and proxy access to GitHub.";
|
|
6599
|
+
return new Error(`Could not check the latest Mason release on GitHub (${detail}).${hint}`, { cause: error });
|
|
6600
|
+
}
|
|
6601
|
+
var init_trust2 = __esm({
|
|
6602
|
+
"src/distribution/trust.ts"() {
|
|
6603
|
+
"use strict";
|
|
6604
|
+
}
|
|
6605
|
+
});
|
|
6606
|
+
|
|
6221
6607
|
// src/distribution/runtime.ts
|
|
6222
6608
|
import fs25 from "fs/promises";
|
|
6223
6609
|
import { unlinkSync } from "fs";
|
|
6224
6610
|
import os5 from "os";
|
|
6225
|
-
import
|
|
6611
|
+
import path32 from "path";
|
|
6226
6612
|
import { randomUUID as randomUUID8 } from "crypto";
|
|
6227
|
-
import { z as
|
|
6613
|
+
import { z as z27 } from "zod";
|
|
6228
6614
|
async function pruneUnusedVersions(home) {
|
|
6229
6615
|
const versions = await storePath(home, "versions");
|
|
6230
6616
|
const candidates = [];
|
|
@@ -6292,7 +6678,8 @@ var init_runtime2 = __esm({
|
|
|
6292
6678
|
init_storage();
|
|
6293
6679
|
init_state();
|
|
6294
6680
|
init_bundle();
|
|
6295
|
-
|
|
6681
|
+
init_trust2();
|
|
6682
|
+
leaseSchema = z27.object({ pid: z27.number().int().positive(), host: z27.string(), bundle: z27.string().regex(/^[a-f0-9]{24}$/) });
|
|
6296
6683
|
}
|
|
6297
6684
|
});
|
|
6298
6685
|
|
|
@@ -6300,7 +6687,7 @@ var init_runtime2 = __esm({
|
|
|
6300
6687
|
import fs26 from "fs/promises";
|
|
6301
6688
|
import os6 from "os";
|
|
6302
6689
|
import { randomUUID as randomUUID9 } from "crypto";
|
|
6303
|
-
import { z as
|
|
6690
|
+
import { z as z28 } from "zod";
|
|
6304
6691
|
async function hasRunningMcp(home) {
|
|
6305
6692
|
const directory = await storePath(home, "sessions");
|
|
6306
6693
|
const files = await fs26.readdir(directory).catch((error) => {
|
|
@@ -6341,7 +6728,7 @@ var init_sessions = __esm({
|
|
|
6341
6728
|
"src/distribution/sessions.ts"() {
|
|
6342
6729
|
"use strict";
|
|
6343
6730
|
init_storage();
|
|
6344
|
-
leaseSchema2 =
|
|
6731
|
+
leaseSchema2 = z28.object({ pid: z28.number().int().positive(), host: z28.string() });
|
|
6345
6732
|
}
|
|
6346
6733
|
});
|
|
6347
6734
|
|
|
@@ -6358,7 +6745,7 @@ __export(install_exports2, {
|
|
|
6358
6745
|
});
|
|
6359
6746
|
import fs27 from "fs/promises";
|
|
6360
6747
|
import os7 from "os";
|
|
6361
|
-
import
|
|
6748
|
+
import path33 from "path";
|
|
6362
6749
|
import { execFile as execFile6, spawn } from "child_process";
|
|
6363
6750
|
import { promisify as promisify6 } from "util";
|
|
6364
6751
|
import { randomUUID as randomUUID10 } from "crypto";
|
|
@@ -6374,7 +6761,7 @@ async function replace(file, content2, mode = 384) {
|
|
|
6374
6761
|
async function installStandalone(source, progress2, options = {}) {
|
|
6375
6762
|
progress2?.step("Checking installation files and location");
|
|
6376
6763
|
const bundle = await verifyBundle(source);
|
|
6377
|
-
const selectedHome =
|
|
6764
|
+
const selectedHome = path33.resolve(defaultHome());
|
|
6378
6765
|
if (!options.stageOnly) await fs27.mkdir(selectedHome, { recursive: true });
|
|
6379
6766
|
const home = await fs27.realpath(selectedHome);
|
|
6380
6767
|
if (options.stageOnly) {
|
|
@@ -6382,8 +6769,8 @@ async function installStandalone(source, progress2, options = {}) {
|
|
|
6382
6769
|
const destination = await storePath(home, "versions/" + bundle.manifestHash.slice(0, 24));
|
|
6383
6770
|
await copyBundle(home, source, destination, bundle);
|
|
6384
6771
|
const { stdout } = await promisify6(execFile6)(
|
|
6385
|
-
|
|
6386
|
-
[
|
|
6772
|
+
path33.join(destination, process.platform === "win32" ? "node.exe" : "node"),
|
|
6773
|
+
[path33.join(destination, "app/dist/mason.js"), "--version"],
|
|
6387
6774
|
{ timeout: 3e4, windowsHide: true, env: { ...process.env, MASON_NO_AUTO_UPDATE: "1" } }
|
|
6388
6775
|
);
|
|
6389
6776
|
if (stdout.trim() !== bundle.manifest.version) throw new Error("Downloaded Mason failed its version health check.");
|
|
@@ -6401,13 +6788,13 @@ async function installLocked2(home, source, bundle, progress2, options, alreadyS
|
|
|
6401
6788
|
if (options.stageOnly) assertUpdatePolicy(previous, bundle.manifest.version, options.policyRevision);
|
|
6402
6789
|
if (!previous) for (const file of await fs27.readdir(home)) {
|
|
6403
6790
|
if (file === ".install-lock") continue;
|
|
6404
|
-
const directory =
|
|
6791
|
+
const directory = path33.join(home, file);
|
|
6405
6792
|
const stat = await fs27.lstat(directory);
|
|
6406
6793
|
if (!["versions", "bin"].includes(file) || !stat.isDirectory() || (await fs27.readdir(directory)).length) {
|
|
6407
6794
|
throw new Error("Mason installation directory is not empty and has no ownership receipt.");
|
|
6408
6795
|
}
|
|
6409
6796
|
}
|
|
6410
|
-
const selectedBin =
|
|
6797
|
+
const selectedBin = path33.resolve(process.env.MASON_BIN_DIR ?? previous?.bin ?? (process.platform === "win32" ? path33.join(home, "bin") : path33.join(os7.homedir(), ".local/bin")));
|
|
6411
6798
|
await fs27.mkdir(selectedBin, { recursive: true });
|
|
6412
6799
|
const bin = await fs27.realpath(selectedBin);
|
|
6413
6800
|
if (previous && (previous.home !== home || previous.bin !== bin)) throw new Error("Installation paths differ from the existing Mason receipt.");
|
|
@@ -6425,10 +6812,10 @@ $previousToken = $env:MASON_UNINSTALL_TOKEN
|
|
|
6425
6812
|
$token = [Guid]::NewGuid().ToString()
|
|
6426
6813
|
$env:MASON_UNINSTALL_TOKEN = $token
|
|
6427
6814
|
try {
|
|
6428
|
-
& ${psQuote(
|
|
6815
|
+
& ${psQuote(path33.join(destination, "node.exe"))} ${psQuote(path33.join(destination, "app/dist/mason.js"))} @args
|
|
6429
6816
|
$code = $LASTEXITCODE
|
|
6430
6817
|
if ($code -eq 0 -and $args.Count -eq 1 -and $args[0] -eq 'uninstall') {
|
|
6431
|
-
if ([IO.File]::ReadAllText(${psQuote(
|
|
6818
|
+
if ([IO.File]::ReadAllText(${psQuote(path33.join(home, ".uninstall-request.txt"))}) -cne $token) { throw 'Mason did not authorize installation cleanup.' }
|
|
6432
6819
|
Remove-Item -LiteralPath ${psQuote(home)} -Recurse -Force
|
|
6433
6820
|
Write-Output ${psQuote("Removed standalone installation: " + home)}
|
|
6434
6821
|
}
|
|
@@ -6437,7 +6824,7 @@ try {
|
|
|
6437
6824
|
finally { $env:MASON_UNINSTALL_TOKEN = $previousToken }
|
|
6438
6825
|
`
|
|
6439
6826
|
} : { mason: `#!/bin/sh
|
|
6440
|
-
exec ${shQuote(
|
|
6827
|
+
exec ${shQuote(path33.join(destination, "node"))} ${shQuote(path33.join(destination, "app/dist/mason.js"))} "$@"
|
|
6441
6828
|
` };
|
|
6442
6829
|
for (const [name, content2] of Object.entries(launchers)) {
|
|
6443
6830
|
const file = await storePath(bin, name);
|
|
@@ -6582,13 +6969,48 @@ async function upgradeStandalone(version, progress2) {
|
|
|
6582
6969
|
progress2?.step("Checking current installation");
|
|
6583
6970
|
const { home, bundle, record } = await currentInstallation();
|
|
6584
6971
|
await verifyBundle(bundle);
|
|
6972
|
+
const mirror = process.env.MASON_RELEASE_BASE || record.updates?.mirror || "";
|
|
6973
|
+
if (mirror && !version) throw new Error("Specify a version when upgrading from a company mirror; no public latest-version lookup was made.");
|
|
6974
|
+
let selected = version?.replace(/^v/, "");
|
|
6975
|
+
if (!selected) {
|
|
6976
|
+
progress2?.step("Checking the latest release");
|
|
6977
|
+
const response = await fetch("https://github.com/adrianczuczka/mason/releases/latest", {
|
|
6978
|
+
method: "HEAD",
|
|
6979
|
+
redirect: "follow",
|
|
6980
|
+
signal: AbortSignal.timeout(3e4)
|
|
6981
|
+
}).catch((error) => {
|
|
6982
|
+
throw releaseRequestError(error);
|
|
6983
|
+
});
|
|
6984
|
+
if (!response.ok) throw new Error(`Latest release request failed (HTTP ${response.status}).`);
|
|
6985
|
+
selected = new URL(response.url).pathname.split("/").pop()?.replace(/^v/, "");
|
|
6986
|
+
if (!selected || !/^\d+\.\d+\.\d+(?:-[A-Za-z0-9.-]+)?$/.test(selected)) throw new Error("Invalid Mason release version.");
|
|
6987
|
+
}
|
|
6988
|
+
const alreadyInstalled = await withLock(home, ".install-lock", async () => {
|
|
6989
|
+
const current = await readInstallation(home);
|
|
6990
|
+
if (current.version !== selected) return false;
|
|
6991
|
+
await verifyBundle(await storePath(home, "versions/" + current.current));
|
|
6992
|
+
current.updates = {
|
|
6993
|
+
...current.updates ?? { enabled: false },
|
|
6994
|
+
revision: randomUUID10(),
|
|
6995
|
+
pinnedVersion: version ? selected : void 0,
|
|
6996
|
+
mirror: mirror || void 0
|
|
6997
|
+
};
|
|
6998
|
+
delete current.pending;
|
|
6999
|
+
await writeStoreJson(home, "install.json", current);
|
|
7000
|
+
return true;
|
|
7001
|
+
});
|
|
7002
|
+
if (alreadyInstalled) {
|
|
7003
|
+
progress2?.stop();
|
|
7004
|
+
console.log(`Mason ${selected} is already installed.`);
|
|
7005
|
+
return 0;
|
|
7006
|
+
}
|
|
6585
7007
|
const windows = process.platform === "win32";
|
|
6586
|
-
const script =
|
|
7008
|
+
const script = path33.join(bundle, windows ? "install.ps1" : "install.sh");
|
|
6587
7009
|
progress2?.stop();
|
|
6588
|
-
|
|
7010
|
+
const code = await new Promise((resolve, reject) => {
|
|
6589
7011
|
const child = spawn(
|
|
6590
7012
|
windows ? "powershell.exe" : "sh",
|
|
6591
|
-
windows ? ["-NoProfile", "-NonInteractive", "-ExecutionPolicy", "Bypass", "-File", script] : [script],
|
|
7013
|
+
windows ? ["-NoProfile", "-NonInteractive", "-ExecutionPolicy", "Bypass", "-File", script, selected] : [script, selected],
|
|
6592
7014
|
{ stdio: "inherit", windowsHide: true, env: {
|
|
6593
7015
|
...process.env,
|
|
6594
7016
|
MASON_HOME: home,
|
|
@@ -6600,8 +7022,10 @@ async function upgradeStandalone(version, progress2) {
|
|
|
6600
7022
|
} }
|
|
6601
7023
|
);
|
|
6602
7024
|
child.on("error", reject);
|
|
6603
|
-
child.on("exit", (
|
|
7025
|
+
child.on("exit", (code2) => resolve(code2 ?? 2));
|
|
6604
7026
|
});
|
|
7027
|
+
if (code === 0) console.log("Restart running assistants to use this installation. Existing project integrations use mason from PATH.");
|
|
7028
|
+
return code;
|
|
6605
7029
|
}
|
|
6606
7030
|
async function uninstallStandalone() {
|
|
6607
7031
|
const { home, record } = await currentInstallation();
|
|
@@ -6636,6 +7060,7 @@ var init_install2 = __esm({
|
|
|
6636
7060
|
init_path();
|
|
6637
7061
|
init_state();
|
|
6638
7062
|
init_sessions();
|
|
7063
|
+
init_trust2();
|
|
6639
7064
|
init_state();
|
|
6640
7065
|
shQuote = (s) => "'" + s.replace(/'/g, "'\\''") + "'";
|
|
6641
7066
|
psQuote = (s) => "'" + s.replace(/'/g, "''") + "'";
|
|
@@ -6658,10 +7083,10 @@ __export(updates_exports, {
|
|
|
6658
7083
|
updateStatus: () => updateStatus
|
|
6659
7084
|
});
|
|
6660
7085
|
import fs28 from "fs/promises";
|
|
6661
|
-
import
|
|
7086
|
+
import path34 from "path";
|
|
6662
7087
|
import { spawn as spawn2 } from "child_process";
|
|
6663
7088
|
import { randomUUID as randomUUID11 } from "crypto";
|
|
6664
|
-
import { z as
|
|
7089
|
+
import { z as z29 } from "zod";
|
|
6665
7090
|
async function boundedDownload(url) {
|
|
6666
7091
|
const response = await fetch(url, { signal: AbortSignal.timeout(15e3), redirect: "follow" });
|
|
6667
7092
|
if (!response.ok || !response.body) throw new Error(`Update metadata request failed (HTTP ${response.status}).`);
|
|
@@ -6712,7 +7137,7 @@ async function runInstaller(home, version, digest3, revision) {
|
|
|
6712
7137
|
const record = await readInstallation(home);
|
|
6713
7138
|
const bundle = await storePath(home, "versions/" + record.current);
|
|
6714
7139
|
await verifyBundle(bundle);
|
|
6715
|
-
const windows = process.platform === "win32", script =
|
|
7140
|
+
const windows = process.platform === "win32", script = path34.join(bundle, windows ? "install.ps1" : "install.sh");
|
|
6716
7141
|
await new Promise((resolve, reject) => {
|
|
6717
7142
|
const child = spawn2(windows ? "powershell.exe" : "sh", windows ? ["-NoProfile", "-NonInteractive", "-ExecutionPolicy", "Bypass", "-File", script] : [script], {
|
|
6718
7143
|
cwd: home,
|
|
@@ -6764,7 +7189,7 @@ async function runAutomaticUpdate(home, dependencies = { fetchRelease: fetchVeri
|
|
|
6764
7189
|
} catch (error) {
|
|
6765
7190
|
status.error = error instanceof Error ? error.message : String(error);
|
|
6766
7191
|
}
|
|
6767
|
-
if (await fs28.access(
|
|
7192
|
+
if (await fs28.access(path34.join(home, "install.json")).then(() => true, () => false)) await writeStoreJson(home, "update-status.json", status);
|
|
6768
7193
|
}, 0);
|
|
6769
7194
|
}
|
|
6770
7195
|
async function scheduleAutomaticUpdate() {
|
|
@@ -6776,8 +7201,8 @@ async function scheduleAutomaticUpdate() {
|
|
|
6776
7201
|
if (status.success && status.data.lastAttempt <= now && now - status.data.lastAttempt < CHECK_INTERVAL_MS) return;
|
|
6777
7202
|
const bundle = await storePath(home, "versions/" + record.current);
|
|
6778
7203
|
const child = spawn2(
|
|
6779
|
-
|
|
6780
|
-
[
|
|
7204
|
+
path34.join(bundle, process.platform === "win32" ? "node.exe" : "node"),
|
|
7205
|
+
[path34.join(bundle, "app/dist/mason.js"), "internal-update"],
|
|
6781
7206
|
{ cwd: home, env: process.env, stdio: "ignore", detached: true, windowsHide: true }
|
|
6782
7207
|
);
|
|
6783
7208
|
child.on("error", () => {
|
|
@@ -6822,8 +7247,8 @@ async function startMcpWithUpdates(start) {
|
|
|
6822
7247
|
const session = await prepareStandaloneMcp(current.home);
|
|
6823
7248
|
if (session.bundle !== current.bundle) {
|
|
6824
7249
|
const child = spawn2(
|
|
6825
|
-
|
|
6826
|
-
[
|
|
7250
|
+
path34.join(session.bundle, process.platform === "win32" ? "node.exe" : "node"),
|
|
7251
|
+
[path34.join(session.bundle, "app/dist/mason-mcp.js"), ...process.argv.slice(2)],
|
|
6827
7252
|
{ stdio: "inherit", windowsHide: true }
|
|
6828
7253
|
);
|
|
6829
7254
|
const interrupt = () => {
|
|
@@ -6874,14 +7299,14 @@ var init_updates = __esm({
|
|
|
6874
7299
|
CHECK_INTERVAL_MS = 24 * 60 * 60 * 1e3;
|
|
6875
7300
|
RELEASE_AGE_MS = 24 * 60 * 60 * 1e3;
|
|
6876
7301
|
releaseBase = "https://github.com/adrianczuczka/mason/releases";
|
|
6877
|
-
releaseListSchema =
|
|
6878
|
-
updateManifestSchema =
|
|
6879
|
-
format:
|
|
6880
|
-
version:
|
|
6881
|
-
publishedAt:
|
|
6882
|
-
assets:
|
|
7302
|
+
releaseListSchema = z29.array(z29.object({ tag_name: z29.string(), draft: z29.boolean(), prerelease: z29.boolean(), published_at: z29.string().nullable() }));
|
|
7303
|
+
updateManifestSchema = z29.object({
|
|
7304
|
+
format: z29.literal(1),
|
|
7305
|
+
version: z29.string().regex(/^\d+\.\d+\.\d+$/),
|
|
7306
|
+
publishedAt: z29.string().datetime(),
|
|
7307
|
+
assets: z29.record(z29.string().regex(/^[a-f0-9]{64}$/))
|
|
6883
7308
|
}).strict();
|
|
6884
|
-
statusSchema2 =
|
|
7309
|
+
statusSchema2 = z29.object({ lastAttempt: z29.number(), availableVersion: z29.string().optional(), error: z29.string().optional() });
|
|
6885
7310
|
}
|
|
6886
7311
|
});
|
|
6887
7312
|
|
|
@@ -7053,7 +7478,7 @@ async function runAutomationCli(argv, stdin = "", io = defaultIo, options = {})
|
|
|
7053
7478
|
const { setupStatus: setupStatus2, summarizeActivation: summarizeActivation2 } = await Promise.resolve().then(() => (init_status(), status_exports));
|
|
7054
7479
|
const setup = await setupStatus2(dir);
|
|
7055
7480
|
const result = { ...await automationStatus(dir), configured: await installedAutomation(dir), setup };
|
|
7056
|
-
io.out(values.json || !process.stdout.isTTY ? JSON.stringify(result, null, 2) : summarizeActivation2(setup));
|
|
7481
|
+
io.out(values.json || !process.stdout.isTTY ? JSON.stringify(result, null, 2) : [summarizeActivation2(setup), result.knowledge].filter(Boolean).join("\n\n"));
|
|
7057
7482
|
return 0;
|
|
7058
7483
|
}
|
|
7059
7484
|
if (action !== "check") throw new Error("Unknown automation command: " + action);
|
|
@@ -7078,7 +7503,7 @@ var inputArgs = process.argv.slice(2);
|
|
|
7078
7503
|
var managedLaunch = inputArgs[0] === "--setup-host";
|
|
7079
7504
|
var setupHost = managedLaunch ? inputArgs.splice(0, 2)[1] : void 0;
|
|
7080
7505
|
var [command, ...args] = inputArgs;
|
|
7081
|
-
var usage = `Mason ${"0.19.
|
|
7506
|
+
var usage = `Mason ${"0.19.2"}
|
|
7082
7507
|
|
|
7083
7508
|
Usage: mason <command> [options]
|
|
7084
7509
|
|
|
@@ -7107,9 +7532,9 @@ try {
|
|
|
7107
7532
|
await prepareLaunch2(setupHost, { allowInactive: command === "mcp" });
|
|
7108
7533
|
}
|
|
7109
7534
|
}
|
|
7110
|
-
if (command === "internal-integration-version") console.log(JSON.stringify({ protocol: 1, version: "0.19.
|
|
7535
|
+
if (command === "internal-integration-version") console.log(JSON.stringify({ protocol: 1, version: "0.19.2" }));
|
|
7111
7536
|
else if (!command || ["--help", "-h", "help"].includes(command)) console.log(usage);
|
|
7112
|
-
else if (["--version", "-v"].includes(command)) console.log("0.19.
|
|
7537
|
+
else if (["--version", "-v"].includes(command)) console.log("0.19.2");
|
|
7113
7538
|
else if (command === "internal-update") {
|
|
7114
7539
|
if (args.length) throw new Error("internal-update takes no arguments.");
|
|
7115
7540
|
const { currentInstallation: currentInstallation2 } = await Promise.resolve().then(() => (init_state(), state_exports));
|
|
@@ -7176,7 +7601,6 @@ Automatic updates: ${status.effective ? "enabled" : "inactive"} (preference: ${s
|
|
|
7176
7601
|
if (command === "upgrade") {
|
|
7177
7602
|
progress = createProgress();
|
|
7178
7603
|
process.exitCode = await upgradeStandalone2(args[0], progress);
|
|
7179
|
-
if (process.exitCode === 0) console.log("Restart running assistants to use this installation. Existing project integrations use mason from PATH.");
|
|
7180
7604
|
} else console.log((process.platform === "win32" ? "Finishing installation cleanup after runtime exit: " : "Removed standalone installation: ") + await uninstallStandalone2() + ". Project configuration and knowledge were retained. Reinstall Mason to use its integrations.");
|
|
7181
7605
|
}
|
|
7182
7606
|
} else if (["setup", "teardown", "status", "check", "auto"].includes(command)) {
|