mason-context 0.19.2 → 0.20.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 +18 -0
- package/dist/mason-audit.js +16 -10
- package/dist/mason-audit.js.map +1 -1
- package/dist/mason-drift.js.map +1 -1
- package/dist/mason-hook.js.map +1 -1
- package/dist/mason-mcp.js +616 -446
- package/dist/mason-mcp.js.map +1 -1
- package/dist/mason-review.js.map +1 -1
- package/dist/mason-runtime.js +16 -10
- package/dist/mason-runtime.js.map +1 -1
- package/dist/mason.js +1203 -903
- package/dist/mason.js.map +1 -1
- package/package.json +2 -2
- package/dist/mason-auto.js +0 -6474
- package/dist/mason-auto.js.map +0 -1
package/dist/mason.js
CHANGED
|
@@ -10,33 +10,6 @@ var __export = (target, all) => {
|
|
|
10
10
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
11
|
};
|
|
12
12
|
|
|
13
|
-
// src/utils/paths.ts
|
|
14
|
-
import path from "path";
|
|
15
|
-
function normalizeRepoPath(value) {
|
|
16
|
-
const slash = value.replace(/\\/g, "/");
|
|
17
|
-
if (!slash || slash.includes("\0") || path.posix.isAbsolute(slash) || /^[A-Za-z]:/.test(slash)) return null;
|
|
18
|
-
if (slash.split("/").includes("..")) return null;
|
|
19
|
-
const normalized = path.posix.normalize(slash).replace(/\/$/, "");
|
|
20
|
-
return normalized === "." ? null : normalized;
|
|
21
|
-
}
|
|
22
|
-
function isWithinRoot(root, candidate) {
|
|
23
|
-
const relative = path.relative(root, candidate);
|
|
24
|
-
return relative !== ".." && !relative.startsWith(`..${path.sep}`) && !path.isAbsolute(relative);
|
|
25
|
-
}
|
|
26
|
-
function anchorMatches(anchor, file) {
|
|
27
|
-
const a = normalizeRepoPath(anchor);
|
|
28
|
-
const f = normalizeRepoPath(file);
|
|
29
|
-
return a !== null && f !== null && (a === f || f.startsWith(`${a}/`));
|
|
30
|
-
}
|
|
31
|
-
function matchingPaths(anchors, files) {
|
|
32
|
-
return [...new Set(files)].filter((file) => anchors.some((anchor) => anchorMatches(anchor, file)));
|
|
33
|
-
}
|
|
34
|
-
var init_paths = __esm({
|
|
35
|
-
"src/utils/paths.ts"() {
|
|
36
|
-
"use strict";
|
|
37
|
-
}
|
|
38
|
-
});
|
|
39
|
-
|
|
40
13
|
// src/utils/profile.ts
|
|
41
14
|
import { AsyncLocalStorage } from "async_hooks";
|
|
42
15
|
async function profilePhase(label, run) {
|
|
@@ -96,6 +69,33 @@ var init_git_read = __esm({
|
|
|
96
69
|
}
|
|
97
70
|
});
|
|
98
71
|
|
|
72
|
+
// src/utils/paths.ts
|
|
73
|
+
import path from "path";
|
|
74
|
+
function normalizeRepoPath(value) {
|
|
75
|
+
const slash = value.replace(/\\/g, "/");
|
|
76
|
+
if (!slash || slash.includes("\0") || path.posix.isAbsolute(slash) || /^[A-Za-z]:/.test(slash)) return null;
|
|
77
|
+
if (slash.split("/").includes("..")) return null;
|
|
78
|
+
const normalized = path.posix.normalize(slash).replace(/\/$/, "");
|
|
79
|
+
return normalized === "." ? null : normalized;
|
|
80
|
+
}
|
|
81
|
+
function isWithinRoot(root, candidate) {
|
|
82
|
+
const relative = path.relative(root, candidate);
|
|
83
|
+
return relative !== ".." && !relative.startsWith(`..${path.sep}`) && !path.isAbsolute(relative);
|
|
84
|
+
}
|
|
85
|
+
function anchorMatches(anchor, file) {
|
|
86
|
+
const a = normalizeRepoPath(anchor);
|
|
87
|
+
const f = normalizeRepoPath(file);
|
|
88
|
+
return a !== null && f !== null && (a === f || f.startsWith(`${a}/`));
|
|
89
|
+
}
|
|
90
|
+
function matchingPaths(anchors, files) {
|
|
91
|
+
return [...new Set(files)].filter((file) => anchors.some((anchor) => anchorMatches(anchor, file)));
|
|
92
|
+
}
|
|
93
|
+
var init_paths = __esm({
|
|
94
|
+
"src/utils/paths.ts"() {
|
|
95
|
+
"use strict";
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
|
|
99
99
|
// src/audit/types.ts
|
|
100
100
|
var types_exports = {};
|
|
101
101
|
__export(types_exports, {
|
|
@@ -146,9 +146,9 @@ async function readBoundedFile(file, maxBytes) {
|
|
|
146
146
|
async function loadProjectConfig(root) {
|
|
147
147
|
try {
|
|
148
148
|
const canonicalRoot = await fs.realpath(root);
|
|
149
|
-
const
|
|
150
|
-
if (!isWithinRoot(canonicalRoot,
|
|
151
|
-
const raw = await readBoundedFile(
|
|
149
|
+
const configPath3 = await fs.realpath(path2.join(root, ".mason/config.json"));
|
|
150
|
+
if (!isWithinRoot(canonicalRoot, configPath3)) throw new Error("Project configuration resolves outside the repository");
|
|
151
|
+
const raw = await readBoundedFile(configPath3, 64 * 1024);
|
|
152
152
|
if (raw === null) throw new Error("Project configuration is not a regular file or exceeds 64 KiB");
|
|
153
153
|
const value = JSON.parse(raw);
|
|
154
154
|
if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error("Expected a configuration object");
|
|
@@ -311,392 +311,39 @@ async function readStoreJson(root, relative) {
|
|
|
311
311
|
const file = await storePath(root, relative);
|
|
312
312
|
const raw = await readBoundedFile(file, 10 * 1024 * 1024);
|
|
313
313
|
if (raw === null) throw new Error("file is not regular or exceeds 10 MiB");
|
|
314
|
-
const parsed = JSON.parse(raw);
|
|
315
|
-
if (parsed === null) throw new Error("expected a JSON object, received null");
|
|
316
|
-
return parsed;
|
|
317
|
-
} catch (error) {
|
|
318
|
-
if (error.code === "ENOENT") return null;
|
|
319
|
-
throw new Error(`Invalid Mason store ${relative}: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
|
|
320
|
-
}
|
|
321
|
-
}
|
|
322
|
-
async function writeStoreJson(root, relative, value) {
|
|
323
|
-
const payload = JSON.stringify(value, null, 2) + "\n";
|
|
324
|
-
if (Buffer.byteLength(payload) > 10 * 1024 * 1024) {
|
|
325
|
-
throw new Error(`Mason store ${relative} exceeds 10 MiB`);
|
|
326
|
-
}
|
|
327
|
-
const file = await storePath(root, relative, true);
|
|
328
|
-
const temporary = path3.join(path3.dirname(file), `.${path3.basename(file)}.${randomUUID()}.tmp`);
|
|
329
|
-
try {
|
|
330
|
-
const handle = await fs2.open(temporary, "wx", 384);
|
|
331
|
-
try {
|
|
332
|
-
await handle.writeFile(payload, "utf8");
|
|
333
|
-
await handle.sync();
|
|
334
|
-
} finally {
|
|
335
|
-
await handle.close();
|
|
336
|
-
}
|
|
337
|
-
await fs2.rename(temporary, file);
|
|
338
|
-
} finally {
|
|
339
|
-
await fs2.rm(temporary, { force: true });
|
|
340
|
-
}
|
|
341
|
-
}
|
|
342
|
-
var init_storage = __esm({
|
|
343
|
-
"src/utils/storage.ts"() {
|
|
344
|
-
"use strict";
|
|
345
|
-
init_paths();
|
|
346
|
-
init_files();
|
|
347
|
-
}
|
|
348
|
-
});
|
|
349
|
-
|
|
350
|
-
// src/utils/store-lock.ts
|
|
351
|
-
import fs3 from "fs/promises";
|
|
352
|
-
import os from "os";
|
|
353
|
-
async function lockDiagnostic(file) {
|
|
354
|
-
let reason2;
|
|
355
|
-
try {
|
|
356
|
-
const owner = JSON.parse(await fs3.readFile(file, "utf8"));
|
|
357
|
-
if (!owner || typeof owner.host !== "string" || !Number.isInteger(owner.pid) || owner.pid <= 0) {
|
|
358
|
-
reason2 = "Lock owner data is incomplete or malformed.";
|
|
359
|
-
} else if (owner.host !== os.hostname()) {
|
|
360
|
-
reason2 = "The lock belongs to another host; its process cannot be checked locally.";
|
|
361
|
-
} else {
|
|
362
|
-
try {
|
|
363
|
-
process.kill(owner.pid, 0);
|
|
364
|
-
reason2 = `Local owner PID ${owner.pid} is still running.`;
|
|
365
|
-
} catch (error) {
|
|
366
|
-
reason2 = error.code === "ESRCH" ? `Local owner PID ${owner.pid} has exited, but lock recovery has not completed.` : `Local owner PID ${owner.pid} could not be checked.`;
|
|
367
|
-
}
|
|
368
|
-
}
|
|
369
|
-
} catch (error) {
|
|
370
|
-
reason2 = error instanceof SyntaxError ? "Lock owner data is incomplete or malformed." : "Lock owner data could not be read; the lock may have changed.";
|
|
371
|
-
}
|
|
372
|
-
if (await fs3.lstat(file + ".reclaim").then(() => true, () => false)) {
|
|
373
|
-
reason2 += " A recovery guard also needs inspection: " + file + ".reclaim.";
|
|
374
|
-
}
|
|
375
|
-
return reason2 + " Retry if a writer is active. For manual recovery, stop all Mason writers for this checkout, confirm they have exited (including other hosts), then remove only confirmed abandoned lock and lock.reclaim files. See docs/reference.md#snapshot-repairs-and-verification.";
|
|
376
|
-
}
|
|
377
|
-
async function withStoreLock(root, directory, run, waitMs = 5e3, label = "Mason store") {
|
|
378
|
-
const file = await storePath(root, directory + "/lock", true);
|
|
379
|
-
const deadline = Date.now() + waitMs;
|
|
380
|
-
let handle;
|
|
381
|
-
while (!handle) {
|
|
382
|
-
try {
|
|
383
|
-
handle = await fs3.open(file, "wx", 384);
|
|
384
|
-
try {
|
|
385
|
-
await handle.writeFile(JSON.stringify({ pid: process.pid, host: os.hostname() }));
|
|
386
|
-
} catch (error) {
|
|
387
|
-
await handle.close().catch(() => {
|
|
388
|
-
});
|
|
389
|
-
handle = void 0;
|
|
390
|
-
await fs3.rm(file, { force: true }).catch(() => {
|
|
391
|
-
});
|
|
392
|
-
throw error;
|
|
393
|
-
}
|
|
394
|
-
} catch (error) {
|
|
395
|
-
if (error.code !== "EEXIST") throw error;
|
|
396
|
-
try {
|
|
397
|
-
const owner = JSON.parse(await fs3.readFile(file, "utf8"));
|
|
398
|
-
if (owner.host === os.hostname() && Number.isInteger(owner.pid) && owner.pid > 0) {
|
|
399
|
-
try {
|
|
400
|
-
process.kill(owner.pid, 0);
|
|
401
|
-
} catch (probe) {
|
|
402
|
-
if (probe.code === "ESRCH") {
|
|
403
|
-
const reclaim = file + ".reclaim";
|
|
404
|
-
let guard;
|
|
405
|
-
try {
|
|
406
|
-
guard = await fs3.open(reclaim, "wx", 384);
|
|
407
|
-
const current = JSON.parse(await fs3.readFile(file, "utf8"));
|
|
408
|
-
if (current.pid === owner.pid && current.host === owner.host) await fs3.unlink(file);
|
|
409
|
-
} finally {
|
|
410
|
-
if (guard) {
|
|
411
|
-
await guard.close();
|
|
412
|
-
await fs3.rm(reclaim, { force: true });
|
|
413
|
-
}
|
|
414
|
-
}
|
|
415
|
-
}
|
|
416
|
-
}
|
|
417
|
-
}
|
|
418
|
-
} catch {
|
|
419
|
-
}
|
|
420
|
-
if (Date.now() >= deadline) throw new Error(label + " is busy or its lock needs inspection: " + file + ". " + await lockDiagnostic(file));
|
|
421
|
-
await new Promise((resolve) => setTimeout(resolve, 40));
|
|
422
|
-
}
|
|
423
|
-
}
|
|
424
|
-
try {
|
|
425
|
-
return await run();
|
|
426
|
-
} finally {
|
|
427
|
-
await handle.close();
|
|
428
|
-
await fs3.unlink(file);
|
|
429
|
-
}
|
|
430
|
-
}
|
|
431
|
-
var init_store_lock = __esm({
|
|
432
|
-
"src/utils/store-lock.ts"() {
|
|
433
|
-
"use strict";
|
|
434
|
-
init_storage();
|
|
435
|
-
}
|
|
436
|
-
});
|
|
437
|
-
|
|
438
|
-
// src/audit/findings.ts
|
|
439
|
-
import { createHash } from "crypto";
|
|
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";
|
|
661
|
-
function parseState(raw) {
|
|
662
|
-
try {
|
|
663
|
-
return stateSchema.parse(raw);
|
|
314
|
+
const parsed = JSON.parse(raw);
|
|
315
|
+
if (parsed === null) throw new Error("expected a JSON object, received null");
|
|
316
|
+
return parsed;
|
|
664
317
|
} catch (error) {
|
|
665
|
-
|
|
318
|
+
if (error.code === "ENOENT") return null;
|
|
319
|
+
throw new Error(`Invalid Mason store ${relative}: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
|
|
666
320
|
}
|
|
667
321
|
}
|
|
668
|
-
function
|
|
669
|
-
|
|
322
|
+
async function writeStoreJson(root, relative, value) {
|
|
323
|
+
const payload = JSON.stringify(value, null, 2) + "\n";
|
|
324
|
+
if (Buffer.byteLength(payload) > 10 * 1024 * 1024) {
|
|
325
|
+
throw new Error(`Mason store ${relative} exceeds 10 MiB`);
|
|
326
|
+
}
|
|
327
|
+
const file = await storePath(root, relative, true);
|
|
328
|
+
const temporary = path3.join(path3.dirname(file), `.${path3.basename(file)}.${randomUUID()}.tmp`);
|
|
329
|
+
try {
|
|
330
|
+
const handle = await fs2.open(temporary, "wx", 384);
|
|
331
|
+
try {
|
|
332
|
+
await handle.writeFile(payload, "utf8");
|
|
333
|
+
await handle.sync();
|
|
334
|
+
} finally {
|
|
335
|
+
await handle.close();
|
|
336
|
+
}
|
|
337
|
+
await fs2.rename(temporary, file);
|
|
338
|
+
} finally {
|
|
339
|
+
await fs2.rm(temporary, { force: true });
|
|
340
|
+
}
|
|
670
341
|
}
|
|
671
|
-
var
|
|
672
|
-
|
|
673
|
-
"src/automation/store.ts"() {
|
|
342
|
+
var init_storage = __esm({
|
|
343
|
+
"src/utils/storage.ts"() {
|
|
674
344
|
"use strict";
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
hostSchema = z3.enum(["claude", "codex"]);
|
|
678
|
-
events = ["session_start", "turn_start", "before_tool", "after_tool", "task_end"];
|
|
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({
|
|
686
|
-
host: hostSchema,
|
|
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() }))
|
|
692
|
-
})),
|
|
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()
|
|
699
|
-
});
|
|
345
|
+
init_paths();
|
|
346
|
+
init_files();
|
|
700
347
|
}
|
|
701
348
|
});
|
|
702
349
|
|
|
@@ -752,12 +399,12 @@ var init_inspection = __esm({
|
|
|
752
399
|
});
|
|
753
400
|
|
|
754
401
|
// src/audit/inputs.ts
|
|
755
|
-
import
|
|
402
|
+
import fs3 from "fs/promises";
|
|
756
403
|
import * as nativeFs from "fs";
|
|
757
404
|
import path5 from "path";
|
|
758
405
|
import fg3 from "fast-glob";
|
|
759
406
|
async function auditInputPath(root, file) {
|
|
760
|
-
if (file === ".") return
|
|
407
|
+
if (file === ".") return fs3.realpath(root);
|
|
761
408
|
try {
|
|
762
409
|
return await storePath(root, file);
|
|
763
410
|
} catch (error) {
|
|
@@ -780,15 +427,15 @@ async function readAuditInput(root, file) {
|
|
|
780
427
|
async function auditGlob(root, patterns, options) {
|
|
781
428
|
if (Array.isArray(patterns) && !patterns.length) return [];
|
|
782
429
|
const directories = /* @__PURE__ */ new Set();
|
|
783
|
-
const readdir2 = ((
|
|
784
|
-
const relative = path5.relative(root,
|
|
430
|
+
const readdir2 = ((directory2, opts, callback) => {
|
|
431
|
+
const relative = path5.relative(root, directory2).split(path5.sep).join("/") || ".";
|
|
785
432
|
directories.add(relative);
|
|
786
433
|
if (directories.size > MAX_INPUTS) {
|
|
787
434
|
callback(new Error(`${options.label} exceeds ${MAX_INPUTS.toLocaleString("en-US")} directories while inspecting ${relative}. Narrow this check's inputs.`));
|
|
788
435
|
return;
|
|
789
436
|
}
|
|
790
437
|
auditInputPath(root, relative).then(() => {
|
|
791
|
-
nativeFs.readdir(
|
|
438
|
+
nativeFs.readdir(directory2, opts, callback);
|
|
792
439
|
}, callback);
|
|
793
440
|
});
|
|
794
441
|
const lstat2 = ((file, callback) => {
|
|
@@ -918,25 +565,25 @@ function commandClaims(line, lineNo, cwd, shell = false) {
|
|
|
918
565
|
if (text2.startsWith("#") || /^(?:echo|printf)\b/.test(text2)) continue;
|
|
919
566
|
const cd = text2.match(/^cd\s+(.+)$/);
|
|
920
567
|
if (cd) {
|
|
921
|
-
const
|
|
922
|
-
cwd =
|
|
568
|
+
const directory2 = literal(cd[1]);
|
|
569
|
+
cwd = directory2 === null || cwd === null || /\s/.test(directory2) && !/^["']/.test(cd[1]) ? null : path6.posix.join(cwd ?? ".", directory2);
|
|
923
570
|
continue;
|
|
924
571
|
}
|
|
925
572
|
for (const match of text2.matchAll(COMMAND)) {
|
|
926
573
|
const flags = match[2].trim();
|
|
927
574
|
const selector = flags.match(/^(?:--prefix|--dir|-C)(?:\s+|=)(.+)$/);
|
|
928
|
-
let
|
|
575
|
+
let directory2 = cwd;
|
|
929
576
|
let scopeUnknown;
|
|
930
577
|
if (shell && match.index !== 0 || match[3].startsWith("-")) scopeUnknown = "Unsupported shell wrapper or options before the script.";
|
|
931
578
|
if (selector) {
|
|
932
579
|
const selected = literal(selector[1]);
|
|
933
|
-
|
|
580
|
+
directory2 = selected === null || cwd === null ? null : path6.posix.join(cwd ?? ".", selected);
|
|
934
581
|
} else if (flags) scopeUnknown = "Unsupported package-manager options before run: " + flags;
|
|
935
582
|
const tail = text2.slice(match.index + match[0].length).split(/\s--(?:\s|$)/)[0];
|
|
936
583
|
if (/(?:^|\s)(?:--workspace(?:s)?|-w|--filter|-F|--prefix|--dir|-C|--if-present)(?:\s|=|$)/.test(tail)) {
|
|
937
584
|
scopeUnknown = "Package selection or conditional options after the script need review.";
|
|
938
585
|
}
|
|
939
|
-
if (/[|()]/.test(text2) || /\b(?:pushd|popd|if|for|while)\b/.test(text2) ||
|
|
586
|
+
if (/[|()]/.test(text2) || /\b(?:pushd|popd|if|for|while)\b/.test(text2) || directory2 === null) {
|
|
940
587
|
scopeUnknown = "The command's working directory or shell control flow could not be resolved.";
|
|
941
588
|
}
|
|
942
589
|
claims.push({
|
|
@@ -944,7 +591,7 @@ function commandClaims(line, lineNo, cwd, shell = false) {
|
|
|
944
591
|
invocation: match[0],
|
|
945
592
|
excerpt: match[0],
|
|
946
593
|
line: lineNo,
|
|
947
|
-
...
|
|
594
|
+
...directory2 !== void 0 && directory2 !== null ? { directory: directory2 } : {},
|
|
948
595
|
...scopeUnknown ? { scopeUnknown } : {}
|
|
949
596
|
});
|
|
950
597
|
}
|
|
@@ -1351,7 +998,7 @@ var init_git = __esm({
|
|
|
1351
998
|
});
|
|
1352
999
|
|
|
1353
1000
|
// src/audit/docs.ts
|
|
1354
|
-
import
|
|
1001
|
+
import fs4 from "fs/promises";
|
|
1355
1002
|
import path7 from "path";
|
|
1356
1003
|
import fg5 from "fast-glob";
|
|
1357
1004
|
async function dirtyDocs(resolvedRoot, files) {
|
|
@@ -1423,7 +1070,7 @@ async function localDocPaths(root) {
|
|
|
1423
1070
|
for (const dir of [".", ".claude"]) {
|
|
1424
1071
|
let entries;
|
|
1425
1072
|
try {
|
|
1426
|
-
entries = await
|
|
1073
|
+
entries = await fs4.readdir(await auditInputPath(root, dir), { withFileTypes: true });
|
|
1427
1074
|
} catch (error) {
|
|
1428
1075
|
if (error.code === "ENOENT") continue;
|
|
1429
1076
|
throw error;
|
|
@@ -1487,27 +1134,27 @@ var init_docs = __esm({
|
|
|
1487
1134
|
});
|
|
1488
1135
|
|
|
1489
1136
|
// src/audit/scope.ts
|
|
1490
|
-
import
|
|
1137
|
+
import fs5 from "fs/promises";
|
|
1491
1138
|
import path8 from "path";
|
|
1492
|
-
function scopedPath(
|
|
1139
|
+
function scopedPath(directory2, value) {
|
|
1493
1140
|
if (path8.posix.isAbsolute(value) || value.includes("\\") || /[\x00-\x1f]/.test(value)) return null;
|
|
1494
|
-
const resolved = path8.posix.normalize(path8.posix.join(
|
|
1141
|
+
const resolved = path8.posix.normalize(path8.posix.join(directory2, value));
|
|
1495
1142
|
return resolved === ".." || resolved.startsWith("../") ? null : resolved;
|
|
1496
1143
|
}
|
|
1497
1144
|
function pathClaimScope(doc, claim) {
|
|
1498
|
-
const
|
|
1499
|
-
const local = scopedPath(
|
|
1145
|
+
const directory2 = claim.relativeTo === "document" ? path8.posix.dirname(doc) : documentScope(doc);
|
|
1146
|
+
const local = scopedPath(directory2, claim.path);
|
|
1500
1147
|
if (!local) return null;
|
|
1501
|
-
const candidates = claim.relativeTo === "document" ||
|
|
1148
|
+
const candidates = claim.relativeTo === "document" || directory2 === "." ? [local] : [...new Set([local, scopedPath(".", claim.path)].filter((p) => p !== null))];
|
|
1502
1149
|
return {
|
|
1503
|
-
basis: claim.relativeTo === "document" ? "document-link" :
|
|
1504
|
-
directory,
|
|
1150
|
+
basis: claim.relativeTo === "document" ? "document-link" : directory2 === "." ? "repository" : "document",
|
|
1151
|
+
directory: directory2,
|
|
1505
1152
|
candidates
|
|
1506
1153
|
};
|
|
1507
1154
|
}
|
|
1508
1155
|
async function pathExists(root, file) {
|
|
1509
1156
|
try {
|
|
1510
|
-
await
|
|
1157
|
+
await fs5.access(await auditInputPath(root, file));
|
|
1511
1158
|
return true;
|
|
1512
1159
|
} catch (error) {
|
|
1513
1160
|
if (["ENOENT", "ENOTDIR"].includes(error.code ?? "")) return false;
|
|
@@ -1528,6 +1175,100 @@ var init_scope = __esm({
|
|
|
1528
1175
|
}
|
|
1529
1176
|
});
|
|
1530
1177
|
|
|
1178
|
+
// src/utils/store-lock.ts
|
|
1179
|
+
import fs6 from "fs/promises";
|
|
1180
|
+
import os from "os";
|
|
1181
|
+
async function lockDiagnostic(file) {
|
|
1182
|
+
let reason2;
|
|
1183
|
+
try {
|
|
1184
|
+
const owner = JSON.parse(await fs6.readFile(file, "utf8"));
|
|
1185
|
+
if (!owner || typeof owner.host !== "string" || !Number.isInteger(owner.pid) || owner.pid <= 0) {
|
|
1186
|
+
reason2 = "Lock owner data is incomplete or malformed.";
|
|
1187
|
+
} else if (owner.host !== os.hostname()) {
|
|
1188
|
+
reason2 = "The lock belongs to another host; its process cannot be checked locally.";
|
|
1189
|
+
} else {
|
|
1190
|
+
try {
|
|
1191
|
+
process.kill(owner.pid, 0);
|
|
1192
|
+
reason2 = `Local owner PID ${owner.pid} is still running.`;
|
|
1193
|
+
} catch (error) {
|
|
1194
|
+
reason2 = error.code === "ESRCH" ? `Local owner PID ${owner.pid} has exited, but lock recovery has not completed.` : `Local owner PID ${owner.pid} could not be checked.`;
|
|
1195
|
+
}
|
|
1196
|
+
}
|
|
1197
|
+
} catch (error) {
|
|
1198
|
+
reason2 = error instanceof SyntaxError ? "Lock owner data is incomplete or malformed." : "Lock owner data could not be read; the lock may have changed.";
|
|
1199
|
+
}
|
|
1200
|
+
if (await fs6.lstat(file + ".reclaim").then(() => true, () => false)) {
|
|
1201
|
+
reason2 += " A recovery guard also needs inspection: " + file + ".reclaim.";
|
|
1202
|
+
}
|
|
1203
|
+
return reason2 + " Retry if a writer is active. For manual recovery, stop all Mason writers for this checkout, confirm they have exited (including other hosts), then remove only confirmed abandoned lock and lock.reclaim files. See docs/reference.md#snapshot-repairs-and-verification.";
|
|
1204
|
+
}
|
|
1205
|
+
async function withStoreLock(root, directory2, run, waitMs = 5e3, label = "Mason store") {
|
|
1206
|
+
const file = await storePath(root, directory2 + "/lock", true);
|
|
1207
|
+
const deadline = Date.now() + waitMs;
|
|
1208
|
+
let handle;
|
|
1209
|
+
while (!handle) {
|
|
1210
|
+
try {
|
|
1211
|
+
handle = await fs6.open(file, "wx", 384);
|
|
1212
|
+
} catch (error) {
|
|
1213
|
+
if (process.platform === "win32" && error.code === "EPERM") {
|
|
1214
|
+
if (Date.now() >= deadline) throw error;
|
|
1215
|
+
await new Promise((resolve) => setTimeout(resolve, 40));
|
|
1216
|
+
continue;
|
|
1217
|
+
}
|
|
1218
|
+
if (error.code !== "EEXIST") throw error;
|
|
1219
|
+
try {
|
|
1220
|
+
const owner = JSON.parse(await fs6.readFile(file, "utf8"));
|
|
1221
|
+
if (owner.host === os.hostname() && Number.isInteger(owner.pid) && owner.pid > 0) {
|
|
1222
|
+
try {
|
|
1223
|
+
process.kill(owner.pid, 0);
|
|
1224
|
+
} catch (probe) {
|
|
1225
|
+
if (probe.code === "ESRCH") {
|
|
1226
|
+
const reclaim = file + ".reclaim";
|
|
1227
|
+
let guard;
|
|
1228
|
+
try {
|
|
1229
|
+
guard = await fs6.open(reclaim, "wx", 384);
|
|
1230
|
+
const current = JSON.parse(await fs6.readFile(file, "utf8"));
|
|
1231
|
+
if (current.pid === owner.pid && current.host === owner.host) await fs6.unlink(file);
|
|
1232
|
+
} finally {
|
|
1233
|
+
if (guard) {
|
|
1234
|
+
await guard.close();
|
|
1235
|
+
await fs6.rm(reclaim, { force: true });
|
|
1236
|
+
}
|
|
1237
|
+
}
|
|
1238
|
+
}
|
|
1239
|
+
}
|
|
1240
|
+
}
|
|
1241
|
+
} catch {
|
|
1242
|
+
}
|
|
1243
|
+
if (Date.now() >= deadline) throw new Error(label + " is busy or its lock needs inspection: " + file + ". " + await lockDiagnostic(file));
|
|
1244
|
+
await new Promise((resolve) => setTimeout(resolve, 40));
|
|
1245
|
+
}
|
|
1246
|
+
if (handle) {
|
|
1247
|
+
try {
|
|
1248
|
+
await handle.writeFile(JSON.stringify({ pid: process.pid, host: os.hostname() }));
|
|
1249
|
+
} catch (error) {
|
|
1250
|
+
await handle.close().catch(() => {
|
|
1251
|
+
});
|
|
1252
|
+
await fs6.rm(file, { force: true }).catch(() => {
|
|
1253
|
+
});
|
|
1254
|
+
throw error;
|
|
1255
|
+
}
|
|
1256
|
+
}
|
|
1257
|
+
}
|
|
1258
|
+
try {
|
|
1259
|
+
return await run();
|
|
1260
|
+
} finally {
|
|
1261
|
+
await handle.close();
|
|
1262
|
+
await fs6.unlink(file);
|
|
1263
|
+
}
|
|
1264
|
+
}
|
|
1265
|
+
var init_store_lock = __esm({
|
|
1266
|
+
"src/utils/store-lock.ts"() {
|
|
1267
|
+
"use strict";
|
|
1268
|
+
init_storage();
|
|
1269
|
+
}
|
|
1270
|
+
});
|
|
1271
|
+
|
|
1531
1272
|
// src/snapshot/lock.ts
|
|
1532
1273
|
var init_lock = __esm({
|
|
1533
1274
|
"src/snapshot/lock.ts"() {
|
|
@@ -1614,7 +1355,7 @@ var init_test_map = __esm({
|
|
|
1614
1355
|
|
|
1615
1356
|
// src/snapshot/snapshot.ts
|
|
1616
1357
|
import path10 from "path";
|
|
1617
|
-
import { z
|
|
1358
|
+
import { z } from "zod";
|
|
1618
1359
|
async function inspectSnapshot(rootDir) {
|
|
1619
1360
|
try {
|
|
1620
1361
|
const raw = await readStoreJson(rootDir, ".mason/snapshot.json");
|
|
@@ -1648,30 +1389,30 @@ var init_snapshot = __esm({
|
|
|
1648
1389
|
init_lock();
|
|
1649
1390
|
init_paths();
|
|
1650
1391
|
init_test_map();
|
|
1651
|
-
repoPath =
|
|
1392
|
+
repoPath = z.string().refine((value) => normalizeRepoPath(value) !== null, "Expected a relative repository path");
|
|
1652
1393
|
verificationFields = {
|
|
1653
|
-
refreshedHash:
|
|
1654
|
-
verifiedAt:
|
|
1655
|
-
verifiedHash:
|
|
1656
|
-
verificationToken:
|
|
1657
|
-
verificationFailed:
|
|
1658
|
-
verificationNote:
|
|
1394
|
+
refreshedHash: z.string().optional(),
|
|
1395
|
+
verifiedAt: z.string().optional(),
|
|
1396
|
+
verifiedHash: z.string().optional(),
|
|
1397
|
+
verificationToken: z.string().optional(),
|
|
1398
|
+
verificationFailed: z.boolean().optional(),
|
|
1399
|
+
verificationNote: z.string().optional()
|
|
1659
1400
|
};
|
|
1660
|
-
featureSchema =
|
|
1661
|
-
description:
|
|
1662
|
-
files:
|
|
1663
|
-
tests:
|
|
1664
|
-
type:
|
|
1401
|
+
featureSchema = z.object({
|
|
1402
|
+
description: z.string(),
|
|
1403
|
+
files: z.array(repoPath),
|
|
1404
|
+
tests: z.array(repoPath).optional(),
|
|
1405
|
+
type: z.enum(["capability", "infrastructure"]).optional(),
|
|
1665
1406
|
...verificationFields
|
|
1666
1407
|
}).passthrough();
|
|
1667
|
-
flowSchema =
|
|
1668
|
-
snapshotSchema =
|
|
1669
|
-
version:
|
|
1670
|
-
createdAt:
|
|
1671
|
-
updatedAt:
|
|
1672
|
-
gitHash:
|
|
1673
|
-
features:
|
|
1674
|
-
flows:
|
|
1408
|
+
flowSchema = z.object({ description: z.string(), chain: z.array(repoPath), ...verificationFields }).passthrough();
|
|
1409
|
+
snapshotSchema = z.object({
|
|
1410
|
+
version: z.literal(2),
|
|
1411
|
+
createdAt: z.string(),
|
|
1412
|
+
updatedAt: z.string(),
|
|
1413
|
+
gitHash: z.string(),
|
|
1414
|
+
features: z.record(featureSchema),
|
|
1415
|
+
flows: z.record(flowSchema)
|
|
1675
1416
|
}).passthrough();
|
|
1676
1417
|
}
|
|
1677
1418
|
});
|
|
@@ -2117,19 +1858,19 @@ async function commandInputs(root, docs) {
|
|
|
2117
1858
|
const resolutions = [];
|
|
2118
1859
|
for (const doc of docs) for (const claim of doc.claims.commands) {
|
|
2119
1860
|
const base = documentScope(doc.path);
|
|
2120
|
-
const
|
|
1861
|
+
const directory2 = scopedPath(base, claim.directory ?? ".");
|
|
2121
1862
|
const scope = {
|
|
2122
1863
|
basis: claim.directory === void 0 ? "document" : "explicit",
|
|
2123
|
-
directory:
|
|
1864
|
+
directory: directory2 ?? base,
|
|
2124
1865
|
candidates: []
|
|
2125
1866
|
};
|
|
2126
1867
|
const resolution = { doc: doc.path, claim, scope, manifest: null, scripts: [], elsewhere: [] };
|
|
2127
1868
|
resolutions.push(resolution);
|
|
2128
|
-
if (claim.scopeUnknown ||
|
|
1869
|
+
if (claim.scopeUnknown || directory2 === null) {
|
|
2129
1870
|
resolution.problem = claim.scopeUnknown ?? "The command's directory leaves the repository.";
|
|
2130
1871
|
continue;
|
|
2131
1872
|
}
|
|
2132
|
-
let cursor =
|
|
1873
|
+
let cursor = directory2;
|
|
2133
1874
|
while (true) {
|
|
2134
1875
|
const manifest = path15.posix.join(cursor, "package.json");
|
|
2135
1876
|
scope.candidates.push(manifest);
|
|
@@ -2145,7 +1886,7 @@ async function commandInputs(root, docs) {
|
|
|
2145
1886
|
cursor = path15.posix.dirname(cursor);
|
|
2146
1887
|
}
|
|
2147
1888
|
if (!resolution.manifest && !resolution.problem) {
|
|
2148
|
-
resolution.problem = `No package.json for the command's scope (${
|
|
1889
|
+
resolution.problem = `No package.json for the command's scope (${directory2}).`;
|
|
2149
1890
|
}
|
|
2150
1891
|
if (resolution.problem || resolution.scripts.includes(claim.scriptName) || claim.directory !== void 0) continue;
|
|
2151
1892
|
inventory ??= await commandManifests(root);
|
|
@@ -2483,7 +2224,7 @@ var init_trust = __esm({
|
|
|
2483
2224
|
});
|
|
2484
2225
|
|
|
2485
2226
|
// src/decisions/provenance.ts
|
|
2486
|
-
import { z as
|
|
2227
|
+
import { z as z2 } from "zod";
|
|
2487
2228
|
function decisionContent(record) {
|
|
2488
2229
|
return {
|
|
2489
2230
|
title: record.title,
|
|
@@ -2547,67 +2288,67 @@ var init_provenance = __esm({
|
|
|
2547
2288
|
"use strict";
|
|
2548
2289
|
init_paths();
|
|
2549
2290
|
init_trust();
|
|
2550
|
-
text = (max) =>
|
|
2551
|
-
decisionSourceSchema =
|
|
2552
|
-
kind:
|
|
2291
|
+
text = (max) => z2.string().trim().min(1).max(max);
|
|
2292
|
+
decisionSourceSchema = z2.object({
|
|
2293
|
+
kind: z2.enum(["pull_request", "issue", "incident", "discussion", "document", "other"]),
|
|
2553
2294
|
reference: text(1e3),
|
|
2554
2295
|
note: text(500).optional()
|
|
2555
2296
|
}).strict();
|
|
2556
|
-
attributionSchema =
|
|
2297
|
+
attributionSchema = z2.object({
|
|
2557
2298
|
owner: text(200).nullable().optional(),
|
|
2558
|
-
sources:
|
|
2299
|
+
sources: z2.array(decisionSourceSchema).max(20).optional(),
|
|
2559
2300
|
actor: text(200).optional()
|
|
2560
2301
|
});
|
|
2561
|
-
contentSchema =
|
|
2562
|
-
title:
|
|
2563
|
-
body:
|
|
2564
|
-
category:
|
|
2565
|
-
files:
|
|
2302
|
+
contentSchema = z2.object({
|
|
2303
|
+
title: z2.string().min(1),
|
|
2304
|
+
body: z2.string().min(1),
|
|
2305
|
+
category: z2.enum(["decision", "gotcha", "deprecation", "convention"]),
|
|
2306
|
+
files: z2.array(z2.string().refine((f) => normalizeRepoPath(f) !== null)),
|
|
2566
2307
|
owner: text(200).optional(),
|
|
2567
|
-
sources:
|
|
2308
|
+
sources: z2.array(decisionSourceSchema).max(20)
|
|
2568
2309
|
});
|
|
2569
|
-
approvalSchema =
|
|
2570
|
-
statusSchema =
|
|
2571
|
-
reviewEvidenceSchema =
|
|
2572
|
-
baseHash:
|
|
2573
|
-
headHash:
|
|
2574
|
-
historyAvailable:
|
|
2575
|
-
changedFiles:
|
|
2576
|
-
localChanges:
|
|
2310
|
+
approvalSchema = z2.enum(["unreviewed", "proposed", "accepted"]);
|
|
2311
|
+
statusSchema = z2.enum(["active", "superseded", "retired"]);
|
|
2312
|
+
reviewEvidenceSchema = z2.object({
|
|
2313
|
+
baseHash: z2.string(),
|
|
2314
|
+
headHash: z2.string(),
|
|
2315
|
+
historyAvailable: z2.boolean(),
|
|
2316
|
+
changedFiles: z2.array(z2.string()),
|
|
2317
|
+
localChanges: z2.array(z2.string())
|
|
2577
2318
|
});
|
|
2578
|
-
eventSchema =
|
|
2579
|
-
kind:
|
|
2580
|
-
at:
|
|
2319
|
+
eventSchema = z2.object({
|
|
2320
|
+
kind: z2.enum(["imported", "created", "revised", "accepted", "reaffirmed", "retired", "superseded"]),
|
|
2321
|
+
at: z2.string().datetime(),
|
|
2581
2322
|
actor: text(200).optional(),
|
|
2582
2323
|
note: text(1500).optional(),
|
|
2583
|
-
revision:
|
|
2324
|
+
revision: z2.number().int().positive(),
|
|
2584
2325
|
content: contentSchema,
|
|
2585
2326
|
approval: approvalSchema,
|
|
2586
2327
|
status: statusSchema,
|
|
2587
|
-
refreshedHash:
|
|
2328
|
+
refreshedHash: z2.string(),
|
|
2588
2329
|
evidence: reviewEvidenceSchema.optional()
|
|
2589
2330
|
});
|
|
2590
|
-
legacySchema =
|
|
2591
|
-
version:
|
|
2592
|
-
id:
|
|
2593
|
-
title:
|
|
2594
|
-
body:
|
|
2331
|
+
legacySchema = z2.object({
|
|
2332
|
+
version: z2.literal(1),
|
|
2333
|
+
id: z2.string().regex(/^[a-zA-Z0-9_-]+$/),
|
|
2334
|
+
title: z2.string().min(1),
|
|
2335
|
+
body: z2.string().min(1),
|
|
2595
2336
|
category: contentSchema.shape.category,
|
|
2596
2337
|
files: contentSchema.shape.files,
|
|
2597
|
-
createdAt:
|
|
2598
|
-
updatedAt:
|
|
2599
|
-
refreshedHash:
|
|
2600
|
-
status:
|
|
2601
|
-
supersededBy:
|
|
2338
|
+
createdAt: z2.string(),
|
|
2339
|
+
updatedAt: z2.string(),
|
|
2340
|
+
refreshedHash: z2.string(),
|
|
2341
|
+
status: z2.enum(["active", "superseded"]),
|
|
2342
|
+
supersededBy: z2.string().optional()
|
|
2602
2343
|
}).passthrough();
|
|
2603
2344
|
currentSchema = legacySchema.extend({
|
|
2604
|
-
version:
|
|
2345
|
+
version: z2.literal(2),
|
|
2605
2346
|
status: statusSchema,
|
|
2606
2347
|
approval: approvalSchema,
|
|
2607
|
-
revision:
|
|
2348
|
+
revision: z2.number().int().positive(),
|
|
2608
2349
|
owner: text(200).optional(),
|
|
2609
|
-
sources:
|
|
2610
|
-
history:
|
|
2350
|
+
sources: z2.array(decisionSourceSchema).max(20),
|
|
2351
|
+
history: z2.array(eventSchema).min(1)
|
|
2611
2352
|
}).superRefine((record, ctx) => {
|
|
2612
2353
|
const invalid = (message2) => ctx.addIssue({ code: "custom", message: message2 });
|
|
2613
2354
|
const same = (a, b) => JSON.stringify(a) === JSON.stringify(b);
|
|
@@ -2637,15 +2378,15 @@ var init_provenance = __esm({
|
|
|
2637
2378
|
}
|
|
2638
2379
|
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");
|
|
2639
2380
|
});
|
|
2640
|
-
decisionSchema =
|
|
2381
|
+
decisionSchema = z2.union([legacySchema, currentSchema]);
|
|
2641
2382
|
}
|
|
2642
2383
|
});
|
|
2643
2384
|
|
|
2644
2385
|
// src/decisions/decisions.ts
|
|
2645
2386
|
import fs9 from "fs/promises";
|
|
2646
2387
|
import path18 from "path";
|
|
2647
|
-
import { createHash
|
|
2648
|
-
import { z as
|
|
2388
|
+
import { createHash } from "crypto";
|
|
2389
|
+
import { z as z3 } from "zod";
|
|
2649
2390
|
async function loadDecisionStore(rootDir) {
|
|
2650
2391
|
const records = [];
|
|
2651
2392
|
const diagnostics = [];
|
|
@@ -2681,7 +2422,7 @@ var init_decisions = __esm({
|
|
|
2681
2422
|
TITLE_MAX_CHARS = 80;
|
|
2682
2423
|
BODY_RECOMMENDED_CHARS = 1500;
|
|
2683
2424
|
BODY_MAX_CHARS = 2500;
|
|
2684
|
-
boundedText = (field, max, hint) =>
|
|
2425
|
+
boundedText = (field, max, hint) => z3.string({
|
|
2685
2426
|
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 })
|
|
2686
2427
|
}).trim().min(1, `${field} must be non-empty`).max(max);
|
|
2687
2428
|
decisionTitleSchema = boundedText("title", TITLE_MAX_CHARS, "Tighten it to a specific headline.");
|
|
@@ -2777,38 +2518,310 @@ async function checkDecisionAnchors(ctx) {
|
|
|
2777
2518
|
}
|
|
2778
2519
|
return result;
|
|
2779
2520
|
}
|
|
2780
|
-
var init_decision_anchor = __esm({
|
|
2781
|
-
"src/audit/checks/decision-anchor.ts"() {
|
|
2521
|
+
var init_decision_anchor = __esm({
|
|
2522
|
+
"src/audit/checks/decision-anchor.ts"() {
|
|
2523
|
+
"use strict";
|
|
2524
|
+
init_drift2();
|
|
2525
|
+
init_decisions();
|
|
2526
|
+
init_provenance();
|
|
2527
|
+
init_checks();
|
|
2528
|
+
}
|
|
2529
|
+
});
|
|
2530
|
+
|
|
2531
|
+
// src/audit/checks/index.ts
|
|
2532
|
+
function emptyResult() {
|
|
2533
|
+
return { issues: [], advisories: [], skipped: [] };
|
|
2534
|
+
}
|
|
2535
|
+
var CHECKS;
|
|
2536
|
+
var init_checks = __esm({
|
|
2537
|
+
"src/audit/checks/index.ts"() {
|
|
2538
|
+
"use strict";
|
|
2539
|
+
init_deleted_reference();
|
|
2540
|
+
init_new_module();
|
|
2541
|
+
init_stale_count();
|
|
2542
|
+
init_dead_command();
|
|
2543
|
+
init_deps_changed();
|
|
2544
|
+
init_decision_anchor();
|
|
2545
|
+
CHECKS = {
|
|
2546
|
+
"deleted-reference": checkDeletedReferences,
|
|
2547
|
+
"new-module": checkNewModules,
|
|
2548
|
+
"stale-count": checkStaleCounts,
|
|
2549
|
+
"dead-command": checkDeadCommands,
|
|
2550
|
+
"deps-changed": checkDepsChanged,
|
|
2551
|
+
"decision-anchor-drift": checkDecisionAnchors
|
|
2552
|
+
};
|
|
2553
|
+
}
|
|
2554
|
+
});
|
|
2555
|
+
|
|
2556
|
+
// src/audit/findings.ts
|
|
2557
|
+
import { createHash as createHash2 } from "crypto";
|
|
2558
|
+
import { z as z4 } from "zod";
|
|
2559
|
+
function findingId(finding) {
|
|
2560
|
+
const e = finding.evidence;
|
|
2561
|
+
let key;
|
|
2562
|
+
switch (e.kind) {
|
|
2563
|
+
case "missing-path":
|
|
2564
|
+
key = e.resolvedPath && e.resolvedPath !== e.claimed ? [e.claimed, e.resolvedPath] : e.claimed;
|
|
2565
|
+
break;
|
|
2566
|
+
case "unmentioned-dir":
|
|
2567
|
+
key = e.dir;
|
|
2568
|
+
break;
|
|
2569
|
+
case "count-mismatch":
|
|
2570
|
+
key = [e.unit.replace(/s$/, ""), e.countedFrom];
|
|
2571
|
+
break;
|
|
2572
|
+
case "missing-script":
|
|
2573
|
+
key = e.scope && e.scope.directory !== "." ? [e.scriptName, e.scope.directory] : e.scriptName;
|
|
2574
|
+
break;
|
|
2575
|
+
case "doc-behind-manifests":
|
|
2576
|
+
key = null;
|
|
2577
|
+
break;
|
|
2578
|
+
case "decision-anchor":
|
|
2579
|
+
key = [e.decisionId, e.provenance?.revision, e.provenance?.approval];
|
|
2580
|
+
break;
|
|
2581
|
+
}
|
|
2582
|
+
return digest([finding.type, finding.anchor.doc, key]);
|
|
2583
|
+
}
|
|
2584
|
+
var digest, checkSchema, commitSchema, anchorSchema, count, scopeSchema, evidenceSchema, findingSchema, issueSchema, advisorySchema, checkResultSchema;
|
|
2585
|
+
var init_findings = __esm({
|
|
2586
|
+
"src/audit/findings.ts"() {
|
|
2587
|
+
"use strict";
|
|
2588
|
+
digest = (value) => createHash2("sha256").update(JSON.stringify(value)).digest("hex");
|
|
2589
|
+
checkSchema = z4.enum(["deleted-reference", "new-module", "stale-count", "dead-command", "deps-changed", "decision-anchor-drift"]);
|
|
2590
|
+
commitSchema = z4.object({ hash: z4.string().regex(/^[a-f0-9]{40,64}$/), date: z4.string(), subject: z4.string() });
|
|
2591
|
+
anchorSchema = z4.object({ doc: z4.string(), line: z4.number().int().positive().nullable(), excerpt: z4.string().nullable() });
|
|
2592
|
+
count = z4.number().int().nonnegative();
|
|
2593
|
+
scopeSchema = z4.object({
|
|
2594
|
+
basis: z4.enum(["repository", "document", "document-link", "explicit"]),
|
|
2595
|
+
directory: z4.string(),
|
|
2596
|
+
candidates: z4.array(z4.string())
|
|
2597
|
+
});
|
|
2598
|
+
evidenceSchema = z4.discriminatedUnion("kind", [
|
|
2599
|
+
z4.object({
|
|
2600
|
+
kind: z4.literal("missing-path"),
|
|
2601
|
+
claimed: z4.string(),
|
|
2602
|
+
renamedTo: z4.string().nullable(),
|
|
2603
|
+
deletedInCommit: commitSchema.nullable(),
|
|
2604
|
+
everTracked: z4.boolean(),
|
|
2605
|
+
parentDirExists: z4.boolean(),
|
|
2606
|
+
resolvedPath: z4.string().optional(),
|
|
2607
|
+
scope: scopeSchema.optional()
|
|
2608
|
+
}),
|
|
2609
|
+
z4.object({
|
|
2610
|
+
kind: z4.literal("unmentioned-dir"),
|
|
2611
|
+
dir: z4.string(),
|
|
2612
|
+
sourceFileCount: count,
|
|
2613
|
+
firstCommit: commitSchema.nullable(),
|
|
2614
|
+
checkedDocs: z4.array(z4.string())
|
|
2615
|
+
}),
|
|
2616
|
+
z4.object({
|
|
2617
|
+
kind: z4.literal("count-mismatch"),
|
|
2618
|
+
claimed: count,
|
|
2619
|
+
actual: count,
|
|
2620
|
+
unit: z4.string(),
|
|
2621
|
+
countedFrom: z4.string(),
|
|
2622
|
+
members: z4.array(z4.string())
|
|
2623
|
+
}),
|
|
2624
|
+
z4.object({
|
|
2625
|
+
kind: z4.literal("missing-script"),
|
|
2626
|
+
scriptName: z4.string(),
|
|
2627
|
+
invocation: z4.string(),
|
|
2628
|
+
manifestsChecked: z4.array(z4.string()),
|
|
2629
|
+
availableScripts: z4.array(z4.string()),
|
|
2630
|
+
scope: scopeSchema.optional()
|
|
2631
|
+
}),
|
|
2632
|
+
z4.object({
|
|
2633
|
+
kind: z4.literal("doc-behind-manifests"),
|
|
2634
|
+
docLastCommit: commitSchema,
|
|
2635
|
+
manifestCommits: z4.array(commitSchema.extend({ files: z4.array(z4.string()) })),
|
|
2636
|
+
totalCommits: count,
|
|
2637
|
+
matches: z4.array(z4.object({ dependency: z4.string(), manifest: z4.string(), before: z4.string().nullable(), after: z4.string().nullable(), line: z4.number().int().positive(), excerpt: z4.string() })).optional(),
|
|
2638
|
+
matchingIncomplete: z4.boolean().optional()
|
|
2639
|
+
}),
|
|
2640
|
+
z4.object({
|
|
2641
|
+
kind: z4.literal("decision-anchor"),
|
|
2642
|
+
decisionId: z4.string(),
|
|
2643
|
+
title: z4.string(),
|
|
2644
|
+
changedFiles: z4.array(z4.string()),
|
|
2645
|
+
refreshedHash: z4.string(),
|
|
2646
|
+
provenance: z4.object({}).passthrough().optional()
|
|
2647
|
+
})
|
|
2648
|
+
]);
|
|
2649
|
+
findingSchema = z4.object({ message: z4.string(), anchor: anchorSchema, evidence: evidenceSchema });
|
|
2650
|
+
issueSchema = findingSchema.extend({
|
|
2651
|
+
type: z4.enum(["deleted-reference", "new-module", "stale-count", "dead-command"]),
|
|
2652
|
+
confidence: z4.enum(["certain", "likely"])
|
|
2653
|
+
});
|
|
2654
|
+
advisorySchema = findingSchema.extend({ type: checkSchema, resolution: z4.enum(["recheck", "informational"]).optional() });
|
|
2655
|
+
checkResultSchema = z4.object({
|
|
2656
|
+
issues: z4.array(issueSchema),
|
|
2657
|
+
advisories: z4.array(advisorySchema),
|
|
2658
|
+
suppressedAdvisories: z4.array(advisorySchema).optional(),
|
|
2659
|
+
skipped: z4.array(z4.object({ check: z4.string(), reason: z4.string(), doc: z4.string().optional() }))
|
|
2660
|
+
});
|
|
2661
|
+
}
|
|
2662
|
+
});
|
|
2663
|
+
|
|
2664
|
+
// src/automation/notifications.ts
|
|
2665
|
+
import { z as z5 } from "zod";
|
|
2666
|
+
function condition(finding) {
|
|
2667
|
+
const source = finding.current ?? finding.original;
|
|
2668
|
+
const e = source.evidence;
|
|
2669
|
+
const id = e.kind === "decision-anchor" ? digest([source.type, source.anchor.doc, e.decisionId]) : finding.id;
|
|
2670
|
+
const result = { level: "confidence" in source ? 2 : 1, facts: [] };
|
|
2671
|
+
switch (e.kind) {
|
|
2672
|
+
case "count-mismatch":
|
|
2673
|
+
result.magnitude = Math.abs(e.actual - e.claimed);
|
|
2674
|
+
break;
|
|
2675
|
+
case "unmentioned-dir":
|
|
2676
|
+
result.magnitude = e.sourceFileCount;
|
|
2677
|
+
break;
|
|
2678
|
+
case "decision-anchor":
|
|
2679
|
+
result.facts = [...e.changedFiles].sort();
|
|
2680
|
+
break;
|
|
2681
|
+
case "doc-behind-manifests":
|
|
2682
|
+
result.facts = (e.matches ?? []).map((m) => JSON.stringify([m.manifest, m.dependency, m.after])).sort();
|
|
2683
|
+
break;
|
|
2684
|
+
}
|
|
2685
|
+
return { id, condition: result };
|
|
2686
|
+
}
|
|
2687
|
+
function notificationConditions(findings) {
|
|
2688
|
+
const active = findings.filter((f) => f.status !== "resolved");
|
|
2689
|
+
const currentIds = new Set(active.filter((f) => f.current).map((f) => condition(f).id));
|
|
2690
|
+
const result = {};
|
|
2691
|
+
for (const f of active) {
|
|
2692
|
+
const value = condition(f);
|
|
2693
|
+
if (!f.current && currentIds.has(value.id)) continue;
|
|
2694
|
+
const previous = result[value.id];
|
|
2695
|
+
result[value.id] = previous ? {
|
|
2696
|
+
level: Math.max(previous.level, value.condition.level),
|
|
2697
|
+
facts: [.../* @__PURE__ */ new Set([...previous.facts, ...value.condition.facts])].sort(),
|
|
2698
|
+
...previous.magnitude !== void 0 || value.condition.magnitude !== void 0 ? { magnitude: Math.max(previous.magnitude ?? 0, value.condition.magnitude ?? 0) } : {}
|
|
2699
|
+
} : value.condition;
|
|
2700
|
+
}
|
|
2701
|
+
return result;
|
|
2702
|
+
}
|
|
2703
|
+
function createNotificationState(findings, head) {
|
|
2704
|
+
return { version: 1, baselineHead: head, baseline: notificationConditions(findings), delivered: {}, cleared: [] };
|
|
2705
|
+
}
|
|
2706
|
+
function worsened(current, previous) {
|
|
2707
|
+
return !previous || current.level > previous.level || (current.magnitude ?? 0) > (previous.magnitude ?? 0) || current.facts.some((fact) => !previous.facts.includes(fact));
|
|
2708
|
+
}
|
|
2709
|
+
function completionFindings(state, findings) {
|
|
2710
|
+
const current = notificationConditions(findings);
|
|
2711
|
+
const cleared = new Set(state.cleared);
|
|
2712
|
+
const resolvedIds = new Set(findings.filter((f) => f.status === "resolved").map((f) => condition(f).id));
|
|
2713
|
+
for (const id of Object.keys(state.baseline)) if (resolvedIds.has(id) && !current[id]) cleared.add(id);
|
|
2714
|
+
state.cleared = [...cleared];
|
|
2715
|
+
const selected = [];
|
|
2716
|
+
const selectedIds = /* @__PURE__ */ new Set();
|
|
2717
|
+
const delivered = { ...state.delivered };
|
|
2718
|
+
for (const finding of findings) {
|
|
2719
|
+
const value = condition(finding);
|
|
2720
|
+
const previous = state.delivered[value.id];
|
|
2721
|
+
value.condition = current[value.id] ?? value.condition;
|
|
2722
|
+
if (finding.status === "unverified" || !finding.current && finding.status !== "resolved") {
|
|
2723
|
+
continue;
|
|
2724
|
+
}
|
|
2725
|
+
if (finding.status === "resolved") {
|
|
2726
|
+
if (!current[value.id]) delete delivered[value.id];
|
|
2727
|
+
continue;
|
|
2728
|
+
}
|
|
2729
|
+
const eligible = cleared.has(value.id) || worsened(value.condition, state.baseline[value.id]);
|
|
2730
|
+
if (!eligible) continue;
|
|
2731
|
+
if (!selectedIds.has(value.id) && worsened(value.condition, previous)) {
|
|
2732
|
+
selectedIds.add(value.id);
|
|
2733
|
+
const source = finding.current;
|
|
2734
|
+
selected.push(source.evidence.kind === "decision-anchor" ? { ...finding, current: { ...source, evidence: { ...source.evidence, changedFiles: value.condition.facts } } } : finding);
|
|
2735
|
+
if (selected.length <= MAX_COMPLETION_FINDINGS) delivered[value.id] = value.condition;
|
|
2736
|
+
}
|
|
2737
|
+
}
|
|
2738
|
+
state.delivered = delivered;
|
|
2739
|
+
return selected;
|
|
2740
|
+
}
|
|
2741
|
+
function completionSummary(findings, reportPath) {
|
|
2742
|
+
if (!findings.length) return null;
|
|
2743
|
+
const safe = (value) => value.replace(/[\u0000-\u001f\u007f-\u009f]/g, " ").slice(0, 250);
|
|
2744
|
+
return [
|
|
2745
|
+
`Mason: ${findings.length} new or worsened finding(s) since the retained baseline.`,
|
|
2746
|
+
...findings.slice(0, MAX_COMPLETION_FINDINGS).map((f) => {
|
|
2747
|
+
const source = f.current ?? f.original;
|
|
2748
|
+
if (source.evidence.kind === "decision-anchor") {
|
|
2749
|
+
const evidence = source.evidence;
|
|
2750
|
+
return [
|
|
2751
|
+
`[advisory] Decision "${safe(evidence.title)}" may no longer hold: ${evidence.changedFiles.length} anchored file(s) changed.`,
|
|
2752
|
+
`Inspect the decision and diff with review_decision(action: "prepare", id: ${JSON.stringify(evidence.decisionId)}); acceptance is not required.`,
|
|
2753
|
+
`Changed files: ${safe(evidence.changedFiles.join(", "))}`
|
|
2754
|
+
].join("\n");
|
|
2755
|
+
}
|
|
2756
|
+
return `[${f.status === "unresolved" ? "issue" : "advisory"}] ${safe(source.anchor.doc)}: ${safe(source.message)}`;
|
|
2757
|
+
}),
|
|
2758
|
+
...findings.length > MAX_COMPLETION_FINDINGS ? [`${findings.length - MAX_COMPLETION_FINDINGS} more in the report; pending later feedback.`] : [],
|
|
2759
|
+
`Evidence: ${reportPath}. Explicit audit: mason_automation(action: "check").`,
|
|
2760
|
+
"Inspect findings within the authorized work. Concurrent changes may also contribute; this summary does not establish who introduced them."
|
|
2761
|
+
].join("\n");
|
|
2762
|
+
}
|
|
2763
|
+
var conditionSchema, MAX_COMPLETION_FINDINGS, notificationStateSchema;
|
|
2764
|
+
var init_notifications = __esm({
|
|
2765
|
+
"src/automation/notifications.ts"() {
|
|
2782
2766
|
"use strict";
|
|
2783
|
-
|
|
2784
|
-
|
|
2785
|
-
|
|
2786
|
-
|
|
2767
|
+
init_findings();
|
|
2768
|
+
conditionSchema = z5.object({
|
|
2769
|
+
level: z5.number(),
|
|
2770
|
+
facts: z5.array(z5.string()),
|
|
2771
|
+
magnitude: z5.number().optional()
|
|
2772
|
+
});
|
|
2773
|
+
MAX_COMPLETION_FINDINGS = 4;
|
|
2774
|
+
notificationStateSchema = z5.object({
|
|
2775
|
+
version: z5.literal(1),
|
|
2776
|
+
baselineHead: z5.string(),
|
|
2777
|
+
baseline: z5.record(conditionSchema),
|
|
2778
|
+
delivered: z5.record(conditionSchema),
|
|
2779
|
+
cleared: z5.array(z5.string())
|
|
2780
|
+
});
|
|
2787
2781
|
}
|
|
2788
2782
|
});
|
|
2789
2783
|
|
|
2790
|
-
// src/
|
|
2791
|
-
|
|
2792
|
-
|
|
2784
|
+
// src/automation/store.ts
|
|
2785
|
+
import { z as z6 } from "zod";
|
|
2786
|
+
function parseState(raw) {
|
|
2787
|
+
try {
|
|
2788
|
+
return stateSchema.parse(raw);
|
|
2789
|
+
} catch (error) {
|
|
2790
|
+
throw new Error("Invalid automation state; original evidence was retained.", { cause: error });
|
|
2791
|
+
}
|
|
2793
2792
|
}
|
|
2794
|
-
|
|
2795
|
-
|
|
2796
|
-
|
|
2793
|
+
function withLock(root, directory2, run, waitMs = 5e3) {
|
|
2794
|
+
return withStoreLock(root, directory2, run, waitMs, "Automation");
|
|
2795
|
+
}
|
|
2796
|
+
var hostSchema, events, stateSchema;
|
|
2797
|
+
var init_store = __esm({
|
|
2798
|
+
"src/automation/store.ts"() {
|
|
2797
2799
|
"use strict";
|
|
2798
|
-
|
|
2799
|
-
|
|
2800
|
-
|
|
2801
|
-
|
|
2802
|
-
|
|
2803
|
-
|
|
2804
|
-
|
|
2805
|
-
|
|
2806
|
-
|
|
2807
|
-
|
|
2808
|
-
|
|
2809
|
-
|
|
2810
|
-
|
|
2811
|
-
|
|
2800
|
+
init_store_lock();
|
|
2801
|
+
init_notifications();
|
|
2802
|
+
hostSchema = z6.enum(["claude", "codex"]);
|
|
2803
|
+
events = ["session_start", "turn_start", "before_tool", "after_tool", "task_end"];
|
|
2804
|
+
stateSchema = z6.object({
|
|
2805
|
+
version: z6.literal(1),
|
|
2806
|
+
root: z6.string(),
|
|
2807
|
+
gitDir: z6.string(),
|
|
2808
|
+
branch: z6.string(),
|
|
2809
|
+
baselines: z6.array(z6.object({ path: z6.string(), at: z6.string(), event: z6.string(), fingerprint: z6.string() })).max(128),
|
|
2810
|
+
sessions: z6.record(z6.object({
|
|
2811
|
+
host: hostSchema,
|
|
2812
|
+
lastUsed: z6.string(),
|
|
2813
|
+
notifiedDiagnostics: z6.array(z6.string()).default([]),
|
|
2814
|
+
pending: z6.record(z6.string()),
|
|
2815
|
+
coverageGaps: z6.array(z6.string()),
|
|
2816
|
+
events: z6.record(z6.object({ at: z6.string(), count: z6.number().int().positive() }))
|
|
2817
|
+
})),
|
|
2818
|
+
updatedAt: z6.string(),
|
|
2819
|
+
fingerprint: z6.string().nullable(),
|
|
2820
|
+
latest: z6.string().nullable(),
|
|
2821
|
+
notifications: notificationStateSchema.optional(),
|
|
2822
|
+
agentNotifications: notificationStateSchema.optional(),
|
|
2823
|
+
analysis: z6.object({ id: z6.string(), fingerprint: z6.string(), completedAt: z6.number(), path: z6.string() }).optional()
|
|
2824
|
+
});
|
|
2812
2825
|
}
|
|
2813
2826
|
});
|
|
2814
2827
|
|
|
@@ -3497,18 +3510,248 @@ var init_evidence = __esm({
|
|
|
3497
3510
|
init_dead_command();
|
|
3498
3511
|
init_storage();
|
|
3499
3512
|
init_advisory_review();
|
|
3500
|
-
engineVersion = true ? "0.
|
|
3513
|
+
engineVersion = true ? "0.20.2" : "development";
|
|
3501
3514
|
hash = (value) => createHash3("sha256").update(JSON.stringify(value)).digest("hex");
|
|
3502
3515
|
content = readAuditInput;
|
|
3503
3516
|
cacheSchema = z9.object({ version: z9.literal(1), entries: z9.record(z9.object({ key: z9.string(), result: checkResultSchema })), digest: z9.string() });
|
|
3504
3517
|
}
|
|
3505
3518
|
});
|
|
3506
3519
|
|
|
3520
|
+
// src/automation/usefulness.ts
|
|
3521
|
+
import fs14 from "fs/promises";
|
|
3522
|
+
import { z as z10 } from "zod";
|
|
3523
|
+
async function enabled(root) {
|
|
3524
|
+
const raw = await readStoreJson(root, configPath);
|
|
3525
|
+
return raw !== null && configSchema.parse(raw).enabled;
|
|
3526
|
+
}
|
|
3527
|
+
async function configureUsefulness(dir, value, options = {}) {
|
|
3528
|
+
const ws = await workspace(dir);
|
|
3529
|
+
const configured = await withStoreLock(ws.root, directory, async () => {
|
|
3530
|
+
if (options.onlyIfUnset) {
|
|
3531
|
+
const existing = await readStoreJson(ws.root, configPath);
|
|
3532
|
+
if (existing !== null) return configSchema.parse(existing).enabled;
|
|
3533
|
+
}
|
|
3534
|
+
const ignore = await storePath(ws.root, directory + "/.gitignore", true);
|
|
3535
|
+
try {
|
|
3536
|
+
await fs14.writeFile(ignore, "*\n", { flag: "wx", mode: 384 });
|
|
3537
|
+
} catch (error) {
|
|
3538
|
+
if (error.code !== "EEXIST") throw error;
|
|
3539
|
+
}
|
|
3540
|
+
await writeStoreJson(ws.root, configPath, { version: 1, enabled: value });
|
|
3541
|
+
return value;
|
|
3542
|
+
});
|
|
3543
|
+
return { enabled: configured, storage: directory, note: "Local only. Disabling retains prior observations." };
|
|
3544
|
+
}
|
|
3545
|
+
async function observeUsefulness(root, event, report, agent, completion, elapsedMs, observedAt = (/* @__PURE__ */ new Date()).toISOString()) {
|
|
3546
|
+
if (!event.host || !event.sessionId || !await enabled(root)) return;
|
|
3547
|
+
const ws = await workspace(root);
|
|
3548
|
+
await withStoreLock(ws.root, directory, async () => {
|
|
3549
|
+
if (!await enabled(root)) return;
|
|
3550
|
+
const raw = await readStoreJson(root, statePath(ws));
|
|
3551
|
+
const state = raw === null ? empty() : stateSchema2.parse(raw);
|
|
3552
|
+
const now = observedAt;
|
|
3553
|
+
const key = hash([event.host, event.sessionId]);
|
|
3554
|
+
const session = state.sessions[key] ??= {
|
|
3555
|
+
host: event.host,
|
|
3556
|
+
updatedAt: now,
|
|
3557
|
+
checks: 0,
|
|
3558
|
+
checkMs: 0,
|
|
3559
|
+
findings: {},
|
|
3560
|
+
decisions: [],
|
|
3561
|
+
retrievalObserved: false,
|
|
3562
|
+
receiptIds: [],
|
|
3563
|
+
reportPath: report.reportPath,
|
|
3564
|
+
truncated: false
|
|
3565
|
+
};
|
|
3566
|
+
const newer = now >= session.updatedAt;
|
|
3567
|
+
if (newer) {
|
|
3568
|
+
session.updatedAt = now;
|
|
3569
|
+
session.reportPath = report.reportPath;
|
|
3570
|
+
}
|
|
3571
|
+
session.checks++;
|
|
3572
|
+
session.checkMs += Math.max(0, elapsedMs);
|
|
3573
|
+
if (newer) {
|
|
3574
|
+
if (event.event === "task_end") session.endedAt = now;
|
|
3575
|
+
else delete session.endedAt;
|
|
3576
|
+
}
|
|
3577
|
+
let latestFindings = [];
|
|
3578
|
+
if (!newer && [...agent.slice(0, 4), ...completion.slice(0, 4)].some((f) => !session.findings[f.id])) {
|
|
3579
|
+
try {
|
|
3580
|
+
if (!session.reportPath.startsWith(ws.directory + "/checks/")) throw new Error("Unexpected report path");
|
|
3581
|
+
latestFindings = z10.object({ findings: z10.array(z10.object({ id: z10.string(), status: findingSchema2.shape.status })) }).parse(await readStoreJson(root, session.reportPath)).findings;
|
|
3582
|
+
} catch {
|
|
3583
|
+
}
|
|
3584
|
+
}
|
|
3585
|
+
for (const [channel, findings] of [["agent", agent], ["completion", completion]]) {
|
|
3586
|
+
for (const finding of findings.slice(0, 4)) {
|
|
3587
|
+
if (!session.findings[finding.id] && Object.keys(session.findings).length >= 200) {
|
|
3588
|
+
session.truncated = true;
|
|
3589
|
+
continue;
|
|
3590
|
+
}
|
|
3591
|
+
const saved = session.findings[finding.id] ??= {
|
|
3592
|
+
type: finding.original.type,
|
|
3593
|
+
channels: [],
|
|
3594
|
+
status: newer ? finding.status : latestFindings.find((f) => f.id === finding.id)?.status ?? "unknown",
|
|
3595
|
+
deliveredAt: now,
|
|
3596
|
+
observedAt: newer ? now : session.updatedAt
|
|
3597
|
+
};
|
|
3598
|
+
if (!saved.channels.includes(channel)) saved.channels.push(channel);
|
|
3599
|
+
}
|
|
3600
|
+
}
|
|
3601
|
+
for (const [id, saved] of Object.entries(session.findings)) {
|
|
3602
|
+
if (now < saved.observedAt) continue;
|
|
3603
|
+
const current = report.findings.find((f) => f.id === id);
|
|
3604
|
+
saved.status = current?.status ?? "unknown";
|
|
3605
|
+
saved.observedAt = now;
|
|
3606
|
+
}
|
|
3607
|
+
if (event.retrievedDecisionIds !== void 0 && event.toolId) {
|
|
3608
|
+
const receipt = hash([event.toolId]);
|
|
3609
|
+
if (!session.receiptIds.includes(receipt)) {
|
|
3610
|
+
session.retrievalObserved = true;
|
|
3611
|
+
const decisions = [.../* @__PURE__ */ new Set([...session.decisions, ...event.retrievedDecisionIds.map((id) => hash([id]))])];
|
|
3612
|
+
if (decisions.length > 200) session.truncated = true;
|
|
3613
|
+
session.decisions = decisions.slice(0, 200);
|
|
3614
|
+
session.receiptIds = [...session.receiptIds, receipt].slice(-200);
|
|
3615
|
+
}
|
|
3616
|
+
}
|
|
3617
|
+
const keys = Object.keys(state.sessions).sort((a, b) => state.sessions[b].updatedAt.localeCompare(state.sessions[a].updatedAt));
|
|
3618
|
+
for (const key2 of keys.slice(50)) delete state.sessions[key2];
|
|
3619
|
+
await writeStoreJson(root, statePath(ws), state);
|
|
3620
|
+
});
|
|
3621
|
+
}
|
|
3622
|
+
async function rateUsefulness(dir, sessionId, findingId2, rating) {
|
|
3623
|
+
const ws = await workspace(dir);
|
|
3624
|
+
return withStoreLock(ws.root, directory, async () => {
|
|
3625
|
+
const state = stateSchema2.parse(await readStoreJson(ws.root, statePath(ws)));
|
|
3626
|
+
const finding = state.sessions[sessionId]?.findings[findingId2];
|
|
3627
|
+
if (!finding) throw new Error("Unknown retained session or finding; inspect mason stats --json.");
|
|
3628
|
+
finding.rating = usefulnessRating.parse(rating);
|
|
3629
|
+
await writeStoreJson(ws.root, statePath(ws), state);
|
|
3630
|
+
return { session: sessionId, finding: findingId2, rating };
|
|
3631
|
+
});
|
|
3632
|
+
}
|
|
3633
|
+
async function usefulnessStatus(dir, sessionId) {
|
|
3634
|
+
const ws = await workspace(dir);
|
|
3635
|
+
const raw = await readStoreJson(ws.root, statePath(ws));
|
|
3636
|
+
const state = raw === null ? empty() : stateSchema2.parse(raw);
|
|
3637
|
+
if (sessionId !== void 0 && !Object.hasOwn(state.sessions, sessionId)) throw new Error("Unknown retained session; inspect mason stats --json for session IDs.");
|
|
3638
|
+
const sessions = Object.entries(state.sessions).filter(([id]) => sessionId === void 0 || id === sessionId).sort(([, a], [, b]) => b.updatedAt.localeCompare(a.updatedAt)).map(([id, s]) => ({
|
|
3639
|
+
id,
|
|
3640
|
+
...s,
|
|
3641
|
+
receiptIds: void 0,
|
|
3642
|
+
delivered: Object.keys(s.findings).length,
|
|
3643
|
+
resolved: Object.values(s.findings).filter((f) => f.status === "resolved").length,
|
|
3644
|
+
unknown: Object.values(s.findings).filter((f) => f.status === "unknown" || f.status === "unverified").length,
|
|
3645
|
+
open: Object.values(s.findings).filter((f) => f.status === "unresolved" || f.status === "review-required").length,
|
|
3646
|
+
ratings: Object.fromEntries(usefulnessRating.options.map((rating) => [rating, Object.values(s.findings).filter((f) => f.rating === rating).length])),
|
|
3647
|
+
deferred: Object.values(s.findings).filter((f) => f.rating === "deferred").length,
|
|
3648
|
+
retrievedDecisions: s.retrievalObserved ? s.decisions.length : null,
|
|
3649
|
+
application: "unknown",
|
|
3650
|
+
modelTokens: null
|
|
3651
|
+
}));
|
|
3652
|
+
const sum = (pick) => sessions.reduce((total, s) => total + pick(s), 0);
|
|
3653
|
+
const retrievalObservedSessions = sessions.filter((s) => s.retrievalObserved).length;
|
|
3654
|
+
return {
|
|
3655
|
+
version: 1,
|
|
3656
|
+
enabled: await enabled(ws.root),
|
|
3657
|
+
branch: ws.branch,
|
|
3658
|
+
sessionId: sessionId ?? null,
|
|
3659
|
+
summary: {
|
|
3660
|
+
sessions: sessions.length,
|
|
3661
|
+
delivered: sum((s) => s.delivered),
|
|
3662
|
+
resolved: sum((s) => s.resolved),
|
|
3663
|
+
open: sum((s) => s.open),
|
|
3664
|
+
unknown: sum((s) => s.unknown),
|
|
3665
|
+
ratings: Object.fromEntries(usefulnessRating.options.map((rating) => [rating, sum((s) => s.ratings[rating])])),
|
|
3666
|
+
retrievedDecisions: retrievalObservedSessions ? sum((s) => s.retrievedDecisions ?? 0) : null,
|
|
3667
|
+
retrievalObservedSessions,
|
|
3668
|
+
checks: sum((s) => s.checks),
|
|
3669
|
+
checkMs: sum((s) => s.checkMs),
|
|
3670
|
+
truncatedSessions: sessions.filter((s) => s.truncated).length,
|
|
3671
|
+
application: "unknown",
|
|
3672
|
+
modelTokens: null
|
|
3673
|
+
},
|
|
3674
|
+
note: "Counts sum distinct findings and returned records within each retained session; the same item may appear in multiple sessions. Status reflects the last observed check, not a fresh verification. Up to 50 sessions and 200 findings/records per session are retained. Resolution after a notice does not establish causation. Ratings are supplied feedback, not authenticated judgments. Lesson application, later-session reuse and model-token cost are unknown.",
|
|
3675
|
+
sessions
|
|
3676
|
+
};
|
|
3677
|
+
}
|
|
3678
|
+
function summarizeUsefulness(result) {
|
|
3679
|
+
const s = result.summary;
|
|
3680
|
+
return [
|
|
3681
|
+
`Mason stats \u2014 tracking ${result.enabled ? "enabled" : "disabled"} (local only)`,
|
|
3682
|
+
`${s.sessions} retained session(s)${result.sessionId ? " selected" : " in this branch"}. Counts are summed per session.`,
|
|
3683
|
+
`Findings: ${s.delivered} delivered; ${s.resolved} observed resolved; ${s.open} open; ${s.unknown} unknown.`,
|
|
3684
|
+
`Feedback: ${s.ratings.helpful} helpful; ${s.ratings["already-knew"]} already knew; ${s.ratings.irrelevant} irrelevant; ${s.ratings.deferred} deferred.`,
|
|
3685
|
+
`Decision records returned: ${s.retrievedDecisions ?? "unknown"} (${s.retrievalObservedSessions}/${s.sessions} sessions with retrieval receipts); application unknown.`,
|
|
3686
|
+
`Automation: ${(s.checkMs / 1e3).toFixed(2)} seconds across ${s.checks} observations; model-token cost unknown.`,
|
|
3687
|
+
...s.truncatedSessions ? [`${s.truncatedSessions} session(s) have truncated observations.`] : [],
|
|
3688
|
+
...result.sessionId ? result.sessions.flatMap((session) => [
|
|
3689
|
+
`Session ${session.id} (${session.host}) \u2014 ${session.endedAt ? "last observed stop " + session.endedAt : "no final stop observed"}`,
|
|
3690
|
+
`Evidence: ${session.reportPath}`,
|
|
3691
|
+
...Object.entries(session.findings).map(([id, f]) => ` ${id}: ${f.type}; ${f.status}; ${f.channels.join("/")}${f.rating ? "; rated " + f.rating : ""}`)
|
|
3692
|
+
]) : [s.sessions ? "Use --json for session IDs, then --session <id> for details." : "No retained sessions observed."],
|
|
3693
|
+
"Last observed checks only. Resolution after a notice does not prove Mason caused it."
|
|
3694
|
+
].join("\n");
|
|
3695
|
+
}
|
|
3696
|
+
function retrievedDecisions(tool, response) {
|
|
3697
|
+
if (!/(?:^|__)mason__get_context$/.test(tool ?? "")) return void 0;
|
|
3698
|
+
try {
|
|
3699
|
+
let value = typeof response === "string" ? JSON.parse(response) : response;
|
|
3700
|
+
if (value?.isError) return void 0;
|
|
3701
|
+
if (Array.isArray(value?.content)) {
|
|
3702
|
+
const text2 = value.content.find((item) => item?.type === "text" && typeof item.text === "string")?.text;
|
|
3703
|
+
if (!text2) return void 0;
|
|
3704
|
+
value = JSON.parse(text2);
|
|
3705
|
+
}
|
|
3706
|
+
if (!value || typeof value.decisions !== "object" || !value.decisions || Array.isArray(value.decisions)) return void 0;
|
|
3707
|
+
return Object.keys(value.decisions).filter((id) => /^[a-zA-Z0-9_-]{1,200}$/.test(id)).slice(0, 200);
|
|
3708
|
+
} catch {
|
|
3709
|
+
return void 0;
|
|
3710
|
+
}
|
|
3711
|
+
}
|
|
3712
|
+
var directory, configPath, configSchema, usefulnessRating, findingSchema2, sessionSchema, stateSchema2, empty, statePath;
|
|
3713
|
+
var init_usefulness = __esm({
|
|
3714
|
+
"src/automation/usefulness.ts"() {
|
|
3715
|
+
"use strict";
|
|
3716
|
+
init_evidence();
|
|
3717
|
+
init_storage();
|
|
3718
|
+
init_store_lock();
|
|
3719
|
+
directory = ".mason/local/usefulness";
|
|
3720
|
+
configPath = directory + "/config.json";
|
|
3721
|
+
configSchema = z10.object({ version: z10.literal(1), enabled: z10.boolean() });
|
|
3722
|
+
usefulnessRating = z10.enum(["helpful", "already-knew", "irrelevant", "deferred"]);
|
|
3723
|
+
findingSchema2 = z10.object({
|
|
3724
|
+
type: z10.string(),
|
|
3725
|
+
channels: z10.array(z10.enum(["agent", "completion"])),
|
|
3726
|
+
status: z10.enum(["resolved", "unresolved", "review-required", "unverified", "unknown"]),
|
|
3727
|
+
deliveredAt: z10.string(),
|
|
3728
|
+
observedAt: z10.string(),
|
|
3729
|
+
rating: usefulnessRating.optional()
|
|
3730
|
+
});
|
|
3731
|
+
sessionSchema = z10.object({
|
|
3732
|
+
host: z10.string(),
|
|
3733
|
+
updatedAt: z10.string(),
|
|
3734
|
+
endedAt: z10.string().optional(),
|
|
3735
|
+
checks: z10.number(),
|
|
3736
|
+
checkMs: z10.number(),
|
|
3737
|
+
findings: z10.record(findingSchema2),
|
|
3738
|
+
decisions: z10.array(z10.string()),
|
|
3739
|
+
retrievalObserved: z10.boolean(),
|
|
3740
|
+
receiptIds: z10.array(z10.string()),
|
|
3741
|
+
reportPath: z10.string(),
|
|
3742
|
+
truncated: z10.boolean()
|
|
3743
|
+
});
|
|
3744
|
+
stateSchema2 = z10.object({ version: z10.literal(1), sessions: z10.record(sessionSchema) });
|
|
3745
|
+
empty = () => ({ version: 1, sessions: {} });
|
|
3746
|
+
statePath = (ws) => `${directory}/${hash([ws.root, ws.gitDir, ws.branch]).slice(0, 24)}.json`;
|
|
3747
|
+
}
|
|
3748
|
+
});
|
|
3749
|
+
|
|
3507
3750
|
// src/automation/execution.ts
|
|
3508
3751
|
import os2 from "os";
|
|
3509
|
-
import
|
|
3752
|
+
import fs15 from "fs/promises";
|
|
3510
3753
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
3511
|
-
import { z as
|
|
3754
|
+
import { z as z11 } from "zod";
|
|
3512
3755
|
function parseExecution(raw) {
|
|
3513
3756
|
try {
|
|
3514
3757
|
return executionSchema.parse(raw);
|
|
@@ -3526,15 +3769,15 @@ function automationFailure(error) {
|
|
|
3526
3769
|
codes.add(String(cause.code));
|
|
3527
3770
|
cause = cause.cause;
|
|
3528
3771
|
}
|
|
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
|
|
3772
|
+
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 z11.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";
|
|
3530
3773
|
return { code, message: message2, retryable: ["inputs-changed", "storage-full", "busy", "io-error"].includes(code), receiptRecorded: false };
|
|
3531
3774
|
}
|
|
3532
3775
|
function failureMessage(error) {
|
|
3533
3776
|
const failure = automationFailure(error);
|
|
3534
3777
|
return `Mason automation unavailable [${failure.code}]; evidence capture/verification was not established. ${failure.message}` + (failure.receiptRecorded ? "" : " No durable failure receipt was recorded.");
|
|
3535
3778
|
}
|
|
3536
|
-
async function failureNotifications(root,
|
|
3537
|
-
const raw = await readStoreJson(root,
|
|
3779
|
+
async function failureNotifications(root, directory2) {
|
|
3780
|
+
const raw = await readStoreJson(root, directory2 + "/notifications.json");
|
|
3538
3781
|
return raw === null ? { version: 1, sessions: {} } : notificationsSchema.parse(raw);
|
|
3539
3782
|
}
|
|
3540
3783
|
async function hookFailureMessage(dir, host, sessionId, error) {
|
|
@@ -3564,12 +3807,12 @@ async function hookFailureMessage(dir, host, sessionId, error) {
|
|
|
3564
3807
|
return failureMessage(error);
|
|
3565
3808
|
}
|
|
3566
3809
|
}
|
|
3567
|
-
async function refreshRunning(root,
|
|
3810
|
+
async function refreshRunning(root, directory2, attempts) {
|
|
3568
3811
|
for (const attempt of attempts) {
|
|
3569
3812
|
if (attempt.status !== "running") continue;
|
|
3570
3813
|
let alive = false;
|
|
3571
3814
|
try {
|
|
3572
|
-
const owner = await readStoreJson(root, attempt.lease ? leasePath(
|
|
3815
|
+
const owner = await readStoreJson(root, attempt.lease ? leasePath(directory2, attempt.id) : directory2 + "/lock");
|
|
3573
3816
|
if (attempt.host === os2.hostname() && owner?.pid === attempt.pid && owner.host === attempt.host) {
|
|
3574
3817
|
try {
|
|
3575
3818
|
process.kill(attempt.pid, 0);
|
|
@@ -3583,12 +3826,12 @@ async function refreshRunning(root, directory, attempts) {
|
|
|
3583
3826
|
if (!alive) attempt.status = "unknown";
|
|
3584
3827
|
}
|
|
3585
3828
|
}
|
|
3586
|
-
async function editExecution(root,
|
|
3587
|
-
return withLock(root,
|
|
3588
|
-
const file =
|
|
3829
|
+
async function editExecution(root, directory2, edit) {
|
|
3830
|
+
return withLock(root, directory2 + "/execution-lock", async () => {
|
|
3831
|
+
const file = directory2 + "/execution.json";
|
|
3589
3832
|
const raw = await readStoreJson(root, file);
|
|
3590
3833
|
const log = raw === null ? { version: 1, attempts: [], discardedAttempts: 0 } : parseExecution(raw);
|
|
3591
|
-
await refreshRunning(root,
|
|
3834
|
+
await refreshRunning(root, directory2, log.attempts);
|
|
3592
3835
|
edit(log);
|
|
3593
3836
|
const finished = log.attempts.filter((a) => a.status !== "running");
|
|
3594
3837
|
const discard = new Set(finished.slice(0, Math.max(0, finished.length - 32)).map((a) => a.id));
|
|
@@ -3597,7 +3840,7 @@ async function editExecution(root, directory, edit) {
|
|
|
3597
3840
|
await writeStoreJson(root, file, log);
|
|
3598
3841
|
});
|
|
3599
3842
|
}
|
|
3600
|
-
async function recordExecution(root,
|
|
3843
|
+
async function recordExecution(root, directory2, event, run) {
|
|
3601
3844
|
const started = performance.now();
|
|
3602
3845
|
const attempt = {
|
|
3603
3846
|
id: randomUUID3(),
|
|
@@ -3608,15 +3851,15 @@ async function recordExecution(root, directory, event, run) {
|
|
|
3608
3851
|
status: "running",
|
|
3609
3852
|
lease: true
|
|
3610
3853
|
};
|
|
3611
|
-
const owner = leasePath(
|
|
3612
|
-
const finish = async (changes) => editExecution(root,
|
|
3854
|
+
const owner = leasePath(directory2, attempt.id);
|
|
3855
|
+
const finish = async (changes) => editExecution(root, directory2, (log) => {
|
|
3613
3856
|
const current = log.attempts.find((a) => a.id === attempt.id);
|
|
3614
3857
|
if (!current) throw new Error("Automation execution receipt disappeared; completion could not be recorded.");
|
|
3615
3858
|
Object.assign(current, changes, { finishedAt: (/* @__PURE__ */ new Date()).toISOString(), durationMs: performance.now() - started });
|
|
3616
3859
|
});
|
|
3617
3860
|
await writeStoreJson(root, owner, { pid: attempt.pid, host: attempt.host });
|
|
3618
3861
|
try {
|
|
3619
|
-
await editExecution(root,
|
|
3862
|
+
await editExecution(root, directory2, (log) => {
|
|
3620
3863
|
if (log.attempts.filter((a) => a.status === "running").length >= 128) throw new Error("Too many active automation executions; existing receipts were retained.");
|
|
3621
3864
|
log.attempts.push(attempt);
|
|
3622
3865
|
});
|
|
@@ -3634,15 +3877,15 @@ async function recordExecution(root, directory, event, run) {
|
|
|
3634
3877
|
throw Object.assign(new Error(failure.message, { cause: error }), { failure });
|
|
3635
3878
|
}
|
|
3636
3879
|
} finally {
|
|
3637
|
-
await
|
|
3880
|
+
await fs15.rm(await storePath(root, owner), { force: true }).catch(() => {
|
|
3638
3881
|
});
|
|
3639
3882
|
}
|
|
3640
3883
|
}
|
|
3641
|
-
async function executionStatus(root,
|
|
3642
|
-
const raw = await readStoreJson(root,
|
|
3884
|
+
async function executionStatus(root, directory2) {
|
|
3885
|
+
const raw = await readStoreJson(root, directory2 + "/execution.json");
|
|
3643
3886
|
if (raw === null) return { status: "not-observed", attempts: [] };
|
|
3644
3887
|
const log = parseExecution(raw);
|
|
3645
|
-
await refreshRunning(root,
|
|
3888
|
+
await refreshRunning(root, directory2, log.attempts);
|
|
3646
3889
|
const completed = [...log.attempts].reverse().find((a) => a.status === "completed");
|
|
3647
3890
|
const outstanding = [...log.attempts].reverse().find((a) => ["failed", "unknown"].includes(a.status) && (!completed || (a.finishedAt ?? a.startedAt) >= completed.startedAt));
|
|
3648
3891
|
const status = log.attempts.some((a) => a.status === "running") ? "running" : outstanding?.status ?? log.attempts.at(-1)?.status ?? "not-observed";
|
|
@@ -3655,35 +3898,35 @@ var init_execution = __esm({
|
|
|
3655
3898
|
init_store();
|
|
3656
3899
|
init_evidence();
|
|
3657
3900
|
init_storage();
|
|
3658
|
-
failureSchema =
|
|
3659
|
-
code:
|
|
3660
|
-
message:
|
|
3661
|
-
retryable:
|
|
3662
|
-
receiptRecorded:
|
|
3901
|
+
failureSchema = z11.object({
|
|
3902
|
+
code: z11.enum(["inputs-changed", "storage-full", "busy", "invalid-input", "history-unavailable", "invalid-evidence", "io-error", "internal"]),
|
|
3903
|
+
message: z11.string(),
|
|
3904
|
+
retryable: z11.boolean(),
|
|
3905
|
+
receiptRecorded: z11.boolean()
|
|
3663
3906
|
});
|
|
3664
|
-
attemptSchema =
|
|
3665
|
-
id:
|
|
3666
|
-
lease:
|
|
3667
|
-
event:
|
|
3668
|
-
startedAt:
|
|
3669
|
-
finishedAt:
|
|
3670
|
-
durationMs:
|
|
3671
|
-
pid:
|
|
3672
|
-
host:
|
|
3673
|
-
status:
|
|
3674
|
-
verificationStatus:
|
|
3675
|
-
reportPath:
|
|
3907
|
+
attemptSchema = z11.object({
|
|
3908
|
+
id: z11.string().regex(/^[a-zA-Z0-9_-]+$/),
|
|
3909
|
+
lease: z11.literal(true).optional(),
|
|
3910
|
+
event: z11.string(),
|
|
3911
|
+
startedAt: z11.string(),
|
|
3912
|
+
finishedAt: z11.string().optional(),
|
|
3913
|
+
durationMs: z11.number().nonnegative().optional(),
|
|
3914
|
+
pid: z11.number().int().positive(),
|
|
3915
|
+
host: z11.string(),
|
|
3916
|
+
status: z11.enum(["running", "completed", "failed", "unknown"]),
|
|
3917
|
+
verificationStatus: z11.string().optional(),
|
|
3918
|
+
reportPath: z11.string().optional(),
|
|
3676
3919
|
failure: failureSchema.optional()
|
|
3677
3920
|
});
|
|
3678
|
-
executionSchema =
|
|
3679
|
-
notificationsSchema =
|
|
3680
|
-
signature:
|
|
3921
|
+
executionSchema = z11.object({ version: z11.literal(1), attempts: z11.array(attemptSchema).max(160), discardedAttempts: z11.number().int().nonnegative().default(0) });
|
|
3922
|
+
notificationsSchema = z11.object({ version: z11.literal(1), sessions: z11.record(z11.object({
|
|
3923
|
+
signature: z11.string(),
|
|
3681
3924
|
code: failureSchema.shape.code,
|
|
3682
|
-
message:
|
|
3683
|
-
count:
|
|
3684
|
-
lastAt:
|
|
3925
|
+
message: z11.string(),
|
|
3926
|
+
count: z11.number().int().positive(),
|
|
3927
|
+
lastAt: z11.string()
|
|
3685
3928
|
})) });
|
|
3686
|
-
leasePath = (
|
|
3929
|
+
leasePath = (directory2, id) => `${directory2}/executions/${id}.json`;
|
|
3687
3930
|
}
|
|
3688
3931
|
});
|
|
3689
3932
|
|
|
@@ -3751,12 +3994,12 @@ var init_pending = __esm({
|
|
|
3751
3994
|
|
|
3752
3995
|
// src/automation/runtime.ts
|
|
3753
3996
|
import { randomUUID as randomUUID4 } from "crypto";
|
|
3754
|
-
import { z as
|
|
3997
|
+
import { z as z12 } from "zod";
|
|
3755
3998
|
async function observeReadOnlyTool(dir, event) {
|
|
3756
3999
|
const ws = await profilePhase("automation.workspace", () => workspace(dir));
|
|
3757
4000
|
await withLock(ws.root, ws.directory, async () => {
|
|
3758
|
-
const
|
|
3759
|
-
const raw = await readStoreJson(ws.root,
|
|
4001
|
+
const statePath2 = ws.directory + "/state.json";
|
|
4002
|
+
const raw = await readStoreJson(ws.root, statePath2);
|
|
3760
4003
|
if (raw === null) return;
|
|
3761
4004
|
const state = parseState(raw);
|
|
3762
4005
|
if (state.root !== ws.root || state.gitDir !== ws.gitDir || state.branch !== ws.branch) {
|
|
@@ -3767,7 +4010,7 @@ async function observeReadOnlyTool(dir, event) {
|
|
|
3767
4010
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
3768
4011
|
session.lastUsed = now;
|
|
3769
4012
|
session.events[event.event] = { at: now, count: (session.events[event.event]?.count ?? 0) + 1 };
|
|
3770
|
-
await writeStoreJson(ws.root,
|
|
4013
|
+
await writeStoreJson(ws.root, statePath2, state);
|
|
3771
4014
|
});
|
|
3772
4015
|
return { root: ws.root, directory: ws.directory };
|
|
3773
4016
|
}
|
|
@@ -3782,7 +4025,7 @@ function summarize(report) {
|
|
|
3782
4025
|
}),
|
|
3783
4026
|
...open.length > 4 ? [`${open.length - 4} more findings in the report.`] : [],
|
|
3784
4027
|
...report.diagnostics.slice(0, 2).map(cleanText),
|
|
3785
|
-
`Evidence: ${report.reportPath}. Resume/check with mason_automation(action: "check") or mason
|
|
4028
|
+
`Evidence: ${report.reportPath}. Resume/check with mason_automation(action: "check") or mason check.`,
|
|
3786
4029
|
"Keep original evidence. Address findings relevant to the authorized task; report unrelated findings with a brief scope reason and suggested follow-up. Use review_advisory for authorized assessments; otherwise leave advisories open without approving them."
|
|
3787
4030
|
].join("\n");
|
|
3788
4031
|
}
|
|
@@ -3841,7 +4084,7 @@ async function verifyInputs(ws, inputs) {
|
|
|
3841
4084
|
async function computeAnalysis(ws, state, inputs, event, previousAudit, cacheDiagnostic) {
|
|
3842
4085
|
const baselines = [...state.baselines];
|
|
3843
4086
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
3844
|
-
const
|
|
4087
|
+
const empty2 = {
|
|
3845
4088
|
version: 1,
|
|
3846
4089
|
status: "unavailable",
|
|
3847
4090
|
root: ws.root,
|
|
@@ -3858,7 +4101,7 @@ async function computeAnalysis(ws, state, inputs, event, previousAudit, cacheDia
|
|
|
3858
4101
|
};
|
|
3859
4102
|
if (!baselines.length && Object.values(inputs.docs).every((value) => value === null)) {
|
|
3860
4103
|
await verifyInputs(ws, inputs);
|
|
3861
|
-
return { baselines, report:
|
|
4104
|
+
return { baselines, report: empty2, cache: null };
|
|
3862
4105
|
}
|
|
3863
4106
|
if (ws.branch === "detached" && state.latest) {
|
|
3864
4107
|
const previous = await readStoreJson(ws.root, state.latest);
|
|
@@ -3960,7 +4203,7 @@ async function analyze(ws, inputs, event, startedAt) {
|
|
|
3960
4203
|
async function automate(dir, event) {
|
|
3961
4204
|
const startedAt = Date.now();
|
|
3962
4205
|
const ws = await profilePhase("automation.workspace", () => workspace(dir));
|
|
3963
|
-
|
|
4206
|
+
const output = await recordExecution(ws.root, ws.directory, event.event, () => withRepositoryInspection(ws.root, async () => {
|
|
3964
4207
|
const inputs = await readInputs(ws.root);
|
|
3965
4208
|
const result = await profilePhase("automation.analysis", () => analyze(ws, inputs, event, startedAt));
|
|
3966
4209
|
if (result.shared) {
|
|
@@ -3977,7 +4220,7 @@ async function automate(dir, event) {
|
|
|
3977
4220
|
let baseline = { findings: report.findings, head: report.head };
|
|
3978
4221
|
if (state.latest) {
|
|
3979
4222
|
try {
|
|
3980
|
-
const previous =
|
|
4223
|
+
const previous = z12.object({ findings: z12.array(repairFindingSchema), head: z12.string() }).safeParse(await readStoreJson(ws.root, state.latest));
|
|
3981
4224
|
if (!previous.success) throw new Error("Unsupported saved report");
|
|
3982
4225
|
baseline = previous.data;
|
|
3983
4226
|
} catch {
|
|
@@ -4045,9 +4288,15 @@ async function automate(dir, event) {
|
|
|
4045
4288
|
state.latest = report.reportPath;
|
|
4046
4289
|
if (persistReport) await writeStoreJson(ws.root, report.reportPath, report);
|
|
4047
4290
|
await writeStoreJson(ws.root, ws.directory + "/state.json", state);
|
|
4048
|
-
return { report, message: message2 };
|
|
4291
|
+
return { report, message: message2, observedAt: now, delivery: { agent: feedback.slice(0, 4), completion: selected.slice(0, 4) } };
|
|
4049
4292
|
}));
|
|
4050
4293
|
}));
|
|
4294
|
+
try {
|
|
4295
|
+
await observeUsefulness(ws.root, event, output.report, output.delivery.agent, output.delivery.completion, Date.now() - startedAt, output.observedAt);
|
|
4296
|
+
} catch {
|
|
4297
|
+
if (event.event === "task_end") output.message = [output.message, "Mason: local usefulness tracking unavailable; inspect mason stats --json."].filter(Boolean).join("\n");
|
|
4298
|
+
}
|
|
4299
|
+
return output;
|
|
4051
4300
|
}
|
|
4052
4301
|
async function automationStatus(dir) {
|
|
4053
4302
|
const { pendingKnowledge: pendingKnowledge2 } = await Promise.resolve().then(() => (init_pending(), pending_exports));
|
|
@@ -4088,6 +4337,7 @@ var SCOPE, priority, cleanText, analysisSchema, sharedSchema, baselineDigest;
|
|
|
4088
4337
|
var init_runtime = __esm({
|
|
4089
4338
|
"src/automation/runtime.ts"() {
|
|
4090
4339
|
"use strict";
|
|
4340
|
+
init_usefulness();
|
|
4091
4341
|
init_notifications();
|
|
4092
4342
|
init_profile();
|
|
4093
4343
|
init_inspection();
|
|
@@ -4100,34 +4350,34 @@ var init_runtime = __esm({
|
|
|
4100
4350
|
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.";
|
|
4101
4351
|
priority = { resolved: 0, "review-required": 1, unresolved: 2, unverified: 3 };
|
|
4102
4352
|
cleanText = (text2) => text2.replace(/[\u0000-\u001f\u007f-\u009f]/g, " ").slice(0, 250);
|
|
4103
|
-
analysisSchema =
|
|
4104
|
-
id:
|
|
4105
|
-
fingerprint:
|
|
4106
|
-
completedAt:
|
|
4107
|
-
baselineDigest:
|
|
4353
|
+
analysisSchema = z12.object({
|
|
4354
|
+
id: z12.string(),
|
|
4355
|
+
fingerprint: z12.string(),
|
|
4356
|
+
completedAt: z12.number(),
|
|
4357
|
+
baselineDigest: z12.string(),
|
|
4108
4358
|
currentAudit: reportSchema.nullable().optional(),
|
|
4109
|
-
report:
|
|
4110
|
-
version:
|
|
4111
|
-
status:
|
|
4112
|
-
root:
|
|
4113
|
-
branch:
|
|
4114
|
-
head:
|
|
4115
|
-
baselinePaths:
|
|
4116
|
-
reportPath:
|
|
4117
|
-
findings:
|
|
4118
|
-
diagnostics:
|
|
4119
|
-
checks:
|
|
4120
|
-
ran:
|
|
4121
|
-
reused:
|
|
4122
|
-
skipped:
|
|
4123
|
-
auditReused:
|
|
4359
|
+
report: z12.object({
|
|
4360
|
+
version: z12.literal(1),
|
|
4361
|
+
status: z12.enum(["verified", "issues-remain", "incomplete", "unavailable"]),
|
|
4362
|
+
root: z12.string(),
|
|
4363
|
+
branch: z12.string(),
|
|
4364
|
+
head: z12.string(),
|
|
4365
|
+
baselinePaths: z12.array(z12.string()),
|
|
4366
|
+
reportPath: z12.string(),
|
|
4367
|
+
findings: z12.array(repairFindingSchema),
|
|
4368
|
+
diagnostics: z12.array(z12.string()),
|
|
4369
|
+
checks: z12.object({
|
|
4370
|
+
ran: z12.array(z12.string()),
|
|
4371
|
+
reused: z12.array(z12.string()),
|
|
4372
|
+
skipped: z12.array(z12.object({ check: z12.string(), reason: z12.string(), doc: z12.string().optional() })),
|
|
4373
|
+
auditReused: z12.boolean().optional()
|
|
4124
4374
|
}),
|
|
4125
|
-
counts:
|
|
4126
|
-
capture:
|
|
4127
|
-
scope:
|
|
4375
|
+
counts: z12.object({ resolved: z12.number(), unresolved: z12.number(), "review-required": z12.number(), unverified: z12.number() }),
|
|
4376
|
+
capture: z12.enum(["observed", "unknown"]),
|
|
4377
|
+
scope: z12.string()
|
|
4128
4378
|
})
|
|
4129
4379
|
});
|
|
4130
|
-
sharedSchema =
|
|
4380
|
+
sharedSchema = z12.object({ analysis: analysisSchema, digest: z12.string() });
|
|
4131
4381
|
baselineDigest = async (ws, state) => profilePhase("automation.baselines", async () => hash(await Promise.all(
|
|
4132
4382
|
state.baselines.map(async (b) => [b.path, await readStoreJson(ws.root, b.path)])
|
|
4133
4383
|
)));
|
|
@@ -4135,12 +4385,12 @@ var init_runtime = __esm({
|
|
|
4135
4385
|
});
|
|
4136
4386
|
|
|
4137
4387
|
// src/automation/hook-root.ts
|
|
4138
|
-
import
|
|
4388
|
+
import fs16 from "fs/promises";
|
|
4139
4389
|
import path23 from "path";
|
|
4140
4390
|
async function repositoryRoot(dir) {
|
|
4141
4391
|
if (!path23.isAbsolute(dir)) throw new Error("Hook input working directory must be absolute.");
|
|
4142
4392
|
try {
|
|
4143
|
-
return await
|
|
4393
|
+
return await fs16.realpath((await git3(dir, "rev-parse", "--show-toplevel")).trim());
|
|
4144
4394
|
} catch (error) {
|
|
4145
4395
|
if (error.code === "ENOENT" || /not a git repository/.test(String(error.stderr))) return null;
|
|
4146
4396
|
throw error;
|
|
@@ -4168,8 +4418,8 @@ var init_hook_root = __esm({
|
|
|
4168
4418
|
});
|
|
4169
4419
|
|
|
4170
4420
|
// src/setup/model.ts
|
|
4171
|
-
import { z as
|
|
4172
|
-
import
|
|
4421
|
+
import { z as z13 } from "zod";
|
|
4422
|
+
import fs17 from "fs/promises";
|
|
4173
4423
|
import path24 from "path";
|
|
4174
4424
|
async function loadSetup(root) {
|
|
4175
4425
|
const raw = await readStoreJson(root, SETUP_PATH);
|
|
@@ -4179,20 +4429,20 @@ async function loadSetup(root) {
|
|
|
4179
4429
|
async function effectiveSetup(root) {
|
|
4180
4430
|
const local = await loadSetup(root);
|
|
4181
4431
|
if (local) return { root, setup: local };
|
|
4182
|
-
const common = await
|
|
4183
|
-
const own = await
|
|
4432
|
+
const common = await fs17.realpath(path24.resolve(root, (await git3(root, "rev-parse", "--git-common-dir")).trim()));
|
|
4433
|
+
const own = await fs17.realpath((await git3(root, "rev-parse", "--absolute-git-dir")).trim());
|
|
4184
4434
|
if (common === own) return { root, setup: null };
|
|
4185
4435
|
const primary = (await git3(root, "worktree", "list", "--porcelain", "-z")).split("\0\0")[0].split("\0");
|
|
4186
4436
|
const candidate = primary.find((field) => field.startsWith("worktree "))?.slice(9);
|
|
4187
4437
|
if (!candidate || primary.includes("bare")) return { root, setup: null };
|
|
4188
4438
|
let inheritedRoot;
|
|
4189
4439
|
try {
|
|
4190
|
-
inheritedRoot = await
|
|
4440
|
+
inheritedRoot = await fs17.realpath((await git3(candidate, "rev-parse", "--show-toplevel")).trim());
|
|
4191
4441
|
} catch (error) {
|
|
4192
4442
|
if (/must be run in a work tree/.test(String(error.stderr))) return { root, setup: null };
|
|
4193
4443
|
throw error;
|
|
4194
4444
|
}
|
|
4195
|
-
const inheritedCommon = await
|
|
4445
|
+
const inheritedCommon = await fs17.realpath(path24.resolve(
|
|
4196
4446
|
inheritedRoot,
|
|
4197
4447
|
(await git3(inheritedRoot, "rev-parse", "--git-common-dir")).trim()
|
|
4198
4448
|
));
|
|
@@ -4202,8 +4452,8 @@ async function effectiveSetup(root) {
|
|
|
4202
4452
|
if (inactive && setup) for (const host of inactiveHostsSchema.parse(inactive).hosts) delete setup.hosts[host];
|
|
4203
4453
|
return { root: inheritedRoot, setup };
|
|
4204
4454
|
}
|
|
4205
|
-
async function loadSetupReceipt(root,
|
|
4206
|
-
const raw = await readStoreJson(root,
|
|
4455
|
+
async function loadSetupReceipt(root, directory2, host) {
|
|
4456
|
+
const raw = await readStoreJson(root, directory2 + "/setup-" + host + ".json");
|
|
4207
4457
|
if (raw === null) return null;
|
|
4208
4458
|
const receipt = receiptSchema.parse(raw);
|
|
4209
4459
|
if (receipt.root !== root || receipt.host !== host) throw new Error("Setup receipt belongs to another installation.");
|
|
@@ -4215,29 +4465,29 @@ var init_model = __esm({
|
|
|
4215
4465
|
"use strict";
|
|
4216
4466
|
init_evidence();
|
|
4217
4467
|
init_storage();
|
|
4218
|
-
setupHostSchema =
|
|
4219
|
-
configuredVersion:
|
|
4220
|
-
revision:
|
|
4221
|
-
fingerprint:
|
|
4222
|
-
mcpFingerprint:
|
|
4223
|
-
instructions:
|
|
4468
|
+
setupHostSchema = z13.object({
|
|
4469
|
+
configuredVersion: z13.string(),
|
|
4470
|
+
revision: z13.string().uuid(),
|
|
4471
|
+
fingerprint: z13.string(),
|
|
4472
|
+
mcpFingerprint: z13.string(),
|
|
4473
|
+
instructions: z13.array(z13.string())
|
|
4224
4474
|
});
|
|
4225
|
-
setupSchema =
|
|
4475
|
+
setupSchema = z13.object({ version: z13.literal(2), hosts: z13.object({
|
|
4226
4476
|
codex: setupHostSchema.optional(),
|
|
4227
4477
|
claude: setupHostSchema.optional()
|
|
4228
4478
|
}) });
|
|
4229
4479
|
SETUP_PATH = ".mason/local/setup.json";
|
|
4230
4480
|
INACTIVE_HOSTS_PATH = ".mason/local/inactive-hosts.json";
|
|
4231
|
-
inactiveHostsSchema =
|
|
4232
|
-
receiptSchema =
|
|
4233
|
-
version:
|
|
4234
|
-
host:
|
|
4235
|
-
status:
|
|
4236
|
-
initialReportPath:
|
|
4237
|
-
initialBaselinePaths:
|
|
4238
|
-
root:
|
|
4239
|
-
revision:
|
|
4240
|
-
configuredAt:
|
|
4481
|
+
inactiveHostsSchema = z13.object({ version: z13.literal(1), hosts: z13.array(z13.enum(["claude", "codex"])) });
|
|
4482
|
+
receiptSchema = z13.object({
|
|
4483
|
+
version: z13.literal(1),
|
|
4484
|
+
host: z13.enum(["codex", "claude"]),
|
|
4485
|
+
status: z13.enum(["installing", "configured"]),
|
|
4486
|
+
initialReportPath: z13.string(),
|
|
4487
|
+
initialBaselinePaths: z13.array(z13.string()),
|
|
4488
|
+
root: z13.string(),
|
|
4489
|
+
revision: z13.string().uuid(),
|
|
4490
|
+
configuredAt: z13.string().optional()
|
|
4241
4491
|
});
|
|
4242
4492
|
}
|
|
4243
4493
|
});
|
|
@@ -4255,7 +4505,7 @@ __export(launcher_exports, {
|
|
|
4255
4505
|
});
|
|
4256
4506
|
import { execFile as execFile2 } from "child_process";
|
|
4257
4507
|
import { promisify as promisify2 } from "util";
|
|
4258
|
-
import
|
|
4508
|
+
import fs18 from "fs/promises";
|
|
4259
4509
|
function globalCommand(args2, platform = process.platform) {
|
|
4260
4510
|
return platform === "win32" ? { command: "cmd.exe", args: ["/d", "/s", "/c", "mason", ...args2] } : { command: "mason", args: args2 };
|
|
4261
4511
|
}
|
|
@@ -4275,7 +4525,7 @@ async function installedCommand() {
|
|
|
4275
4525
|
}
|
|
4276
4526
|
}
|
|
4277
4527
|
async function prepareLaunch(host, options = {}) {
|
|
4278
|
-
const root = await
|
|
4528
|
+
const root = await fs18.realpath((await git3(options.dir ?? process.cwd(), "rev-parse", "--show-toplevel")).trim());
|
|
4279
4529
|
const entry = (await effectiveSetup(root)).setup?.hosts[host];
|
|
4280
4530
|
process.env.MASON_SETUP_ROOT = root;
|
|
4281
4531
|
process.env.MASON_SETUP_HOST = host;
|
|
@@ -4298,7 +4548,7 @@ var init_launcher = __esm({
|
|
|
4298
4548
|
"use strict";
|
|
4299
4549
|
init_evidence();
|
|
4300
4550
|
init_model();
|
|
4301
|
-
executingVersion = () => true ? "0.
|
|
4551
|
+
executingVersion = () => true ? "0.20.2" : "development";
|
|
4302
4552
|
mcpCommand = (host) => globalCommand(["--setup-host", host, "mcp"]);
|
|
4303
4553
|
hookCommand = (host) => `mason --setup-host ${host} auto`;
|
|
4304
4554
|
}
|
|
@@ -4311,13 +4561,13 @@ __export(observations_exports, {
|
|
|
4311
4561
|
observeActivation: () => observeActivation,
|
|
4312
4562
|
readObservation: () => readObservation
|
|
4313
4563
|
});
|
|
4314
|
-
import
|
|
4315
|
-
import { z as
|
|
4316
|
-
function observationPath(
|
|
4317
|
-
return `${
|
|
4564
|
+
import fs19 from "fs/promises";
|
|
4565
|
+
import { z as z14 } from "zod";
|
|
4566
|
+
function observationPath(directory2, host, revision, version = executingVersion()) {
|
|
4567
|
+
return `${directory2}/activation/${host}-${revision}-${hash(version).slice(0, 12)}.json`;
|
|
4318
4568
|
}
|
|
4319
|
-
async function readObservation(root,
|
|
4320
|
-
const raw = await readStoreJson(root, observationPath(
|
|
4569
|
+
async function readObservation(root, directory2, host, revision, version = executingVersion()) {
|
|
4570
|
+
const raw = await readStoreJson(root, observationPath(directory2, host, revision, version));
|
|
4321
4571
|
if (raw === null) return null;
|
|
4322
4572
|
const record = observationSchema.parse(raw);
|
|
4323
4573
|
if (record.root !== root || record.host !== host || record.revision !== revision || record.masonVersion !== version) throw new Error("Activation receipt belongs to another installation.");
|
|
@@ -4334,12 +4584,12 @@ async function observeActivation(dir, event, options = {}) {
|
|
|
4334
4584
|
return inactiveSetupMessage();
|
|
4335
4585
|
}
|
|
4336
4586
|
const capturedDirectory = options.directory?.match(/^\.mason\/reports\/automation\/[a-f0-9]{24}$/)?.[0] ?? options.reportPath?.match(/^(\.mason\/reports\/automation\/[a-f0-9]{24})\/checks\//)?.[1];
|
|
4337
|
-
const ws = capturedDirectory ? { root: await
|
|
4338
|
-
if (ws.root !== await
|
|
4587
|
+
const ws = capturedDirectory ? { root: await fs19.realpath(dir), directory: capturedDirectory } : await workspace(dir);
|
|
4588
|
+
if (ws.root !== await fs19.realpath(process.env.MASON_SETUP_ROOT)) return null;
|
|
4339
4589
|
const { setup } = await effectiveSetup(ws.root);
|
|
4340
4590
|
if (setup?.hosts[host]?.revision !== revision) return "Mason setup changed; restart the assistant to observe the current integration.";
|
|
4341
|
-
const
|
|
4342
|
-
await withLock(ws.root,
|
|
4591
|
+
const directory2 = ws.directory + "/activation";
|
|
4592
|
+
await withLock(ws.root, directory2, async () => {
|
|
4343
4593
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
4344
4594
|
const record = await readObservation(ws.root, ws.directory, host, revision) ?? {
|
|
4345
4595
|
version: 1,
|
|
@@ -4383,26 +4633,26 @@ var init_observations = __esm({
|
|
|
4383
4633
|
init_storage();
|
|
4384
4634
|
init_model();
|
|
4385
4635
|
init_launcher();
|
|
4386
|
-
observationSchema =
|
|
4387
|
-
version:
|
|
4388
|
-
masonVersion:
|
|
4389
|
-
root:
|
|
4390
|
-
revision:
|
|
4391
|
-
host:
|
|
4392
|
-
contextCalls:
|
|
4393
|
-
lastContextAt:
|
|
4394
|
-
sessions:
|
|
4395
|
-
events:
|
|
4396
|
-
at:
|
|
4397
|
-
verificationStatus:
|
|
4398
|
-
reportPath:
|
|
4636
|
+
observationSchema = z14.object({
|
|
4637
|
+
version: z14.literal(1),
|
|
4638
|
+
masonVersion: z14.string(),
|
|
4639
|
+
root: z14.string(),
|
|
4640
|
+
revision: z14.string(),
|
|
4641
|
+
host: z14.enum(["codex", "claude"]),
|
|
4642
|
+
contextCalls: z14.number().int().nonnegative(),
|
|
4643
|
+
lastContextAt: z14.string().optional(),
|
|
4644
|
+
sessions: z14.record(z14.object({
|
|
4645
|
+
events: z14.array(z14.enum(events)),
|
|
4646
|
+
at: z14.string(),
|
|
4647
|
+
verificationStatus: z14.string().optional(),
|
|
4648
|
+
reportPath: z14.string().optional()
|
|
4399
4649
|
}))
|
|
4400
4650
|
});
|
|
4401
4651
|
}
|
|
4402
4652
|
});
|
|
4403
4653
|
|
|
4404
4654
|
// src/automation/adapters.ts
|
|
4405
|
-
import { z as
|
|
4655
|
+
import { z as z15 } from "zod";
|
|
4406
4656
|
function normalizeHook(host, raw) {
|
|
4407
4657
|
hostSchema.parse(host);
|
|
4408
4658
|
const input = inputSchema.parse(raw);
|
|
@@ -4412,7 +4662,8 @@ function normalizeHook(host, raw) {
|
|
|
4412
4662
|
host,
|
|
4413
4663
|
sessionId: input.session_id,
|
|
4414
4664
|
toolId: input.tool_use_id,
|
|
4415
|
-
mutating: !!input.tool_name && !readOnly
|
|
4665
|
+
mutating: !!input.tool_name && !readOnly,
|
|
4666
|
+
...input.hook_event_name === "PostToolUse" ? { retrievedDecisionIds: retrievedDecisions(input.tool_name, input.tool_response) } : {}
|
|
4416
4667
|
} };
|
|
4417
4668
|
}
|
|
4418
4669
|
async function runAutomationHook(host, stdin, options = {}) {
|
|
@@ -4469,7 +4720,7 @@ function knownManagedHookCommands(host) {
|
|
|
4469
4720
|
function compatibleManagedHookCommands(host, platform = process.platform) {
|
|
4470
4721
|
return [managedHookCommand(host, platform), `mason --setup-host ${host} auto hook --host ${host}`];
|
|
4471
4722
|
}
|
|
4472
|
-
function hookConfig(host, command2 = "npx --no-install --package mason-context mason
|
|
4723
|
+
function hookConfig(host, command2 = "npx --no-install --package mason-context mason auto") {
|
|
4473
4724
|
const handler = { type: "command", command: command2 === `mason --setup-host ${host} auto` ? managedHookCommand(host) : command2 + " hook --host " + host, timeout: 30 };
|
|
4474
4725
|
return { hooks: Object.fromEntries(HOOK_EVENTS.map((name) => [
|
|
4475
4726
|
name,
|
|
@@ -4480,19 +4731,21 @@ var inputSchema, lifecycle, HOOK_EVENTS;
|
|
|
4480
4731
|
var init_adapters = __esm({
|
|
4481
4732
|
"src/automation/adapters.ts"() {
|
|
4482
4733
|
"use strict";
|
|
4734
|
+
init_usefulness();
|
|
4483
4735
|
init_execution();
|
|
4484
4736
|
init_runtime();
|
|
4485
4737
|
init_store();
|
|
4486
4738
|
init_hook_root();
|
|
4487
|
-
inputSchema =
|
|
4488
|
-
cwd:
|
|
4489
|
-
session_id:
|
|
4490
|
-
hook_event_name:
|
|
4491
|
-
tool_name:
|
|
4492
|
-
tool_use_id:
|
|
4493
|
-
|
|
4494
|
-
|
|
4495
|
-
|
|
4739
|
+
inputSchema = z15.object({
|
|
4740
|
+
cwd: z15.string().min(1),
|
|
4741
|
+
session_id: z15.string().min(1).max(500),
|
|
4742
|
+
hook_event_name: z15.enum(["SessionStart", "UserPromptSubmit", "PreToolUse", "PostToolUse", "Stop"]),
|
|
4743
|
+
tool_name: z15.string().optional(),
|
|
4744
|
+
tool_use_id: z15.string().max(500).optional(),
|
|
4745
|
+
tool_response: z15.unknown().optional(),
|
|
4746
|
+
tool_input: z15.unknown().optional(),
|
|
4747
|
+
stop_hook_active: z15.boolean().optional(),
|
|
4748
|
+
permission_mode: z15.string().optional()
|
|
4496
4749
|
});
|
|
4497
4750
|
lifecycle = {
|
|
4498
4751
|
SessionStart: "session_start",
|
|
@@ -4512,7 +4765,7 @@ __export(files_exports, {
|
|
|
4512
4765
|
managedBlock: () => managedBlock,
|
|
4513
4766
|
readText: () => readText
|
|
4514
4767
|
});
|
|
4515
|
-
import
|
|
4768
|
+
import fs20 from "fs/promises";
|
|
4516
4769
|
import path25 from "path";
|
|
4517
4770
|
import { randomUUID as randomUUID5 } from "crypto";
|
|
4518
4771
|
async function readText(root, file) {
|
|
@@ -4542,14 +4795,14 @@ async function applyEdit(root, edit) {
|
|
|
4542
4795
|
if (edit.after === null) {
|
|
4543
4796
|
const file2 = await storePath(root, edit.path);
|
|
4544
4797
|
if (await readText(root, edit.path) !== edit.before) throw new Error("Input changed during removal: " + edit.path);
|
|
4545
|
-
await
|
|
4798
|
+
await fs20.unlink(file2);
|
|
4546
4799
|
return true;
|
|
4547
4800
|
}
|
|
4548
4801
|
const file = await storePath(root, edit.path, true);
|
|
4549
4802
|
const temporary = path25.join(path25.dirname(file), ".mason-setup-" + randomUUID5() + ".tmp");
|
|
4550
4803
|
try {
|
|
4551
|
-
const mode = await
|
|
4552
|
-
const handle = await
|
|
4804
|
+
const mode = await fs20.stat(file).then((s) => s.mode & 511, () => 384);
|
|
4805
|
+
const handle = await fs20.open(temporary, "wx", mode);
|
|
4553
4806
|
try {
|
|
4554
4807
|
await handle.writeFile(edit.after, "utf8");
|
|
4555
4808
|
await handle.sync();
|
|
@@ -4557,10 +4810,10 @@ async function applyEdit(root, edit) {
|
|
|
4557
4810
|
await handle.close();
|
|
4558
4811
|
}
|
|
4559
4812
|
if (await readText(root, edit.path) !== edit.before) throw new Error("Integration input changed during update: " + edit.path);
|
|
4560
|
-
await
|
|
4813
|
+
await fs20.rename(temporary, file);
|
|
4561
4814
|
return true;
|
|
4562
4815
|
} finally {
|
|
4563
|
-
await
|
|
4816
|
+
await fs20.rm(temporary, { force: true });
|
|
4564
4817
|
}
|
|
4565
4818
|
}
|
|
4566
4819
|
var init_files2 = __esm({
|
|
@@ -4581,7 +4834,7 @@ __export(ownership_exports, {
|
|
|
4581
4834
|
ownershipEdit: () => ownershipEdit,
|
|
4582
4835
|
ownershipSchema: () => ownershipSchema
|
|
4583
4836
|
});
|
|
4584
|
-
import { z as
|
|
4837
|
+
import { z as z16 } from "zod";
|
|
4585
4838
|
import { parse } from "smol-toml";
|
|
4586
4839
|
function blockSpan(text2, start, end) {
|
|
4587
4840
|
const starts = text2.split(start).length - 1, ends = text2.split(end).length - 1;
|
|
@@ -4632,30 +4885,30 @@ var init_ownership = __esm({
|
|
|
4632
4885
|
["<!-- mason:agents:start -->", "<!-- mason:agents:end -->"],
|
|
4633
4886
|
["# mason:mcp:start", "# mason:mcp:end"]
|
|
4634
4887
|
];
|
|
4635
|
-
fileSchema =
|
|
4636
|
-
created:
|
|
4637
|
-
initialHash:
|
|
4638
|
-
blocks:
|
|
4639
|
-
mcpHash:
|
|
4640
|
-
hookCommands:
|
|
4641
|
-
hooks:
|
|
4888
|
+
fileSchema = z16.object({
|
|
4889
|
+
created: z16.boolean(),
|
|
4890
|
+
initialHash: z16.string(),
|
|
4891
|
+
blocks: z16.record(z16.string()),
|
|
4892
|
+
mcpHash: z16.string().optional(),
|
|
4893
|
+
hookCommands: z16.array(z16.string()).optional(),
|
|
4894
|
+
hooks: z16.record(z16.array(z16.string())).optional()
|
|
4642
4895
|
});
|
|
4643
|
-
ownershipSchema =
|
|
4896
|
+
ownershipSchema = z16.object({ version: z16.literal(1), files: z16.record(fileSchema) });
|
|
4644
4897
|
}
|
|
4645
4898
|
});
|
|
4646
4899
|
|
|
4647
4900
|
// src/mcp/init.ts
|
|
4648
|
-
import { z as
|
|
4901
|
+
import { z as z17 } from "zod";
|
|
4649
4902
|
var markerSchema, CLAUDE_MD_SECTION, ASSISTANT_SETUP, QUICKSTART_PLAYBOOK, MAP_PLAYBOOK;
|
|
4650
4903
|
var init_init = __esm({
|
|
4651
4904
|
"src/mcp/init.ts"() {
|
|
4652
4905
|
"use strict";
|
|
4653
4906
|
init_storage();
|
|
4654
4907
|
init_files2();
|
|
4655
|
-
markerSchema =
|
|
4656
|
-
version:
|
|
4657
|
-
initializedAt:
|
|
4658
|
-
features:
|
|
4908
|
+
markerSchema = z17.object({
|
|
4909
|
+
version: z17.literal(1),
|
|
4910
|
+
initializedAt: z17.string(),
|
|
4911
|
+
features: z17.object({ confluence: z17.boolean().optional() }).passthrough().optional()
|
|
4659
4912
|
}).passthrough();
|
|
4660
4913
|
CLAUDE_MD_SECTION = `<!-- mason:start -->
|
|
4661
4914
|
## Mason project knowledge
|
|
@@ -4677,13 +4930,13 @@ Mason provides recorded decisions and file impact over MCP. A concept map is opt
|
|
|
4677
4930
|
|
|
4678
4931
|
- 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
4932
|
|
|
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
|
|
4933
|
+
- 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 status\` / \`mason check\`. Preserve existing baselines across sessions. Automatic checks do not authorize unrelated repairs or approve advisories.
|
|
4681
4934
|
|
|
4682
4935
|
Inspect source for what the retrieved context does not answer.
|
|
4683
4936
|
<!-- mason:end -->`;
|
|
4684
4937
|
ASSISTANT_SETUP = `UNIFIED SETUP
|
|
4685
|
-
When the user requests Mason setup, call mason_init again with mode: "setup" and host: "codex" or "claude" for the assistant being configured. This uses the same engine as mason
|
|
4686
|
-
If the user only requested inspection or review, report findings without running setup. The setup result distinguishes configured from observed activation. Explain the host's native trust step and request a new session; never trust hooks on the user's behalf or claim activation from generated configuration alone. After a normal task finishes, mason_automation(action: "status") or mason
|
|
4938
|
+
When the user requests Mason setup, call mason_init again with mode: "setup" and host: "codex" or "claude" for the assistant being configured. This uses the same engine as mason setup: retain the initial audit before instruction edits, use the installed mason command on PATH, merge project MCP configuration and hooks, update marker-delimited instructions and Git ignore rules, and record setup. A concept map is optional.
|
|
4939
|
+
If the user only requested inspection or review, report findings without running setup. The setup result distinguishes configured from observed activation. Explain the host's native trust step and request a new session; never trust hooks on the user's behalf or claim activation from generated configuration alone. After a normal task finishes, mason_automation(action: "status") or mason status reports observed context use and hook events. Existing findings remain reviewable; setup does not approve advisories or manufacture decisions.
|
|
4687
4940
|
|
|
4688
4941
|
The managed project guidance is:
|
|
4689
4942
|
${CLAUDE_MD_SECTION}`;
|
|
@@ -4744,7 +4997,7 @@ __export(config_exports, {
|
|
|
4744
4997
|
});
|
|
4745
4998
|
import { isDeepStrictEqual } from "util";
|
|
4746
4999
|
import { parse as parse2, stringify } from "smol-toml";
|
|
4747
|
-
import { z as
|
|
5000
|
+
import { z as z18 } from "zod";
|
|
4748
5001
|
async function instructionEdits(root, host) {
|
|
4749
5002
|
const found = await Promise.all(DOC_CANDIDATES.map(async (file) => ({ path: file, text: await readText(root, file) })));
|
|
4750
5003
|
const existing = found.find((f) => f.text !== null);
|
|
@@ -4803,7 +5056,7 @@ async function mcpEdit(root, host) {
|
|
|
4803
5056
|
return { path: file, before, after };
|
|
4804
5057
|
}
|
|
4805
5058
|
async function hookEdits(root, host) {
|
|
4806
|
-
const file =
|
|
5059
|
+
const file = configPath2(host), before = await readText(root, file);
|
|
4807
5060
|
const { planAutomationInstall: planAutomationInstall2 } = await Promise.resolve().then(() => (init_install(), install_exports));
|
|
4808
5061
|
const plan = await planAutomationInstall2(root, host, hookCommand(host));
|
|
4809
5062
|
return [
|
|
@@ -4831,7 +5084,7 @@ async function inspectHostConfig(root, host, plannedMcp) {
|
|
|
4831
5084
|
const text2 = plannedMcp ?? await readText(root, mcpPath(host));
|
|
4832
5085
|
const config = host === "codex" ? parse2(text2 ?? "") : object.parse(JSON.parse(text2 ?? "{}"));
|
|
4833
5086
|
const servers = host === "codex" ? config.mcp_servers : config.mcpServers;
|
|
4834
|
-
const hooks = automationConfigSchema.parse(JSON.parse(await readText(root,
|
|
5087
|
+
const hooks = automationConfigSchema.parse(JSON.parse(await readText(root, configPath2(host)) ?? "{}"));
|
|
4835
5088
|
const expected = hookConfig(host, hookCommand(host));
|
|
4836
5089
|
const compatible = compatibleManagedHookCommands(host);
|
|
4837
5090
|
const mcp = servers?.mason === void 0 ? null : object.parse(servers.mason);
|
|
@@ -4855,7 +5108,7 @@ var init_config = __esm({
|
|
|
4855
5108
|
init_install();
|
|
4856
5109
|
init_files2();
|
|
4857
5110
|
init_launcher();
|
|
4858
|
-
object =
|
|
5111
|
+
object = z18.record(z18.unknown());
|
|
4859
5112
|
mcpPath = (host) => host === "codex" ? ".codex/config.toml" : ".mcp.json";
|
|
4860
5113
|
TOML_START = "# mason:mcp:start";
|
|
4861
5114
|
TOML_END = "# mason:mcp:end";
|
|
@@ -4869,13 +5122,13 @@ var install_exports = {};
|
|
|
4869
5122
|
__export(install_exports, {
|
|
4870
5123
|
AUTOMATION_PATH: () => AUTOMATION_PATH,
|
|
4871
5124
|
automationConfigSchema: () => automationConfigSchema,
|
|
4872
|
-
configPath: () =>
|
|
5125
|
+
configPath: () => configPath2,
|
|
4873
5126
|
installAutomation: () => installAutomation,
|
|
4874
5127
|
installedAutomation: () => installedAutomation,
|
|
4875
5128
|
planAutomationInstall: () => planAutomationInstall,
|
|
4876
5129
|
recordSchema: () => recordSchema2
|
|
4877
5130
|
});
|
|
4878
|
-
import { z as
|
|
5131
|
+
import { z as z19 } from "zod";
|
|
4879
5132
|
async function installationRecord(root) {
|
|
4880
5133
|
return recordSchema2.parse(await readStoreJson(root, AUTOMATION_PATH) ?? { version: 1, hosts: {} });
|
|
4881
5134
|
}
|
|
@@ -4884,7 +5137,7 @@ async function installAutomation(dir, host, command2) {
|
|
|
4884
5137
|
return withLock(ws.root, ".mason/reports/setup-lock", () => installLocked(ws.root, host, command2));
|
|
4885
5138
|
}
|
|
4886
5139
|
async function planAutomationInstall(root, host, command2) {
|
|
4887
|
-
const file =
|
|
5140
|
+
const file = configPath2(host);
|
|
4888
5141
|
const before = await readText(root, file);
|
|
4889
5142
|
const existing = automationConfigSchema.parse(JSON.parse(before ?? "{}"));
|
|
4890
5143
|
const record = await installationRecord(root);
|
|
@@ -4924,7 +5177,7 @@ async function installLocked(root, host, command2) {
|
|
|
4924
5177
|
status: "configured",
|
|
4925
5178
|
command: newCommand,
|
|
4926
5179
|
events: HOOK_EVENTS,
|
|
4927
|
-
next: host === "codex" ? "Review/trust these hooks using Codex /hooks and start a new session. mason
|
|
5180
|
+
next: host === "codex" ? "Review/trust these hooks using Codex /hooks and start a new session. mason status reports observed events separately from configuration." : "Start a new Claude Code session. mason status reports observed events separately from configuration.",
|
|
4928
5181
|
note: "Install mason-context in the project before using the default command. Ignore .mason/reports/ to keep local evidence out of commits. Hooks preserve evidence and suggest scoped repairs; they do not approve edits or decisions."
|
|
4929
5182
|
};
|
|
4930
5183
|
}
|
|
@@ -4935,10 +5188,10 @@ async function installedAutomation(dir) {
|
|
|
4935
5188
|
for (const host of ["claude", "codex"]) {
|
|
4936
5189
|
const expected = record.hosts[host];
|
|
4937
5190
|
if (!expected) continue;
|
|
4938
|
-
const current = automationConfigSchema.parse(await readStoreJson(ws.root,
|
|
5191
|
+
const current = automationConfigSchema.parse(await readStoreJson(ws.root, configPath2(host)) ?? {});
|
|
4939
5192
|
const configuredEvents = HOOK_EVENTS.filter((event) => current.hooks?.[event]?.some((group) => group.hooks.some((handler) => handler.type === "command" && handler.command === expected.command)));
|
|
4940
5193
|
result[host] = {
|
|
4941
|
-
configPath:
|
|
5194
|
+
configPath: configPath2(host),
|
|
4942
5195
|
configuredEvents,
|
|
4943
5196
|
status: current.disableAllHooks === true ? "disabled" : configuredEvents.length === HOOK_EVENTS.length ? "configured" : "incomplete",
|
|
4944
5197
|
runtime: "Host version, trust, policy, and tool coverage still determine execution; inspect observed events."
|
|
@@ -4946,7 +5199,7 @@ async function installedAutomation(dir) {
|
|
|
4946
5199
|
}
|
|
4947
5200
|
return result;
|
|
4948
5201
|
}
|
|
4949
|
-
var groupSchema, automationConfigSchema, recordSchema2, AUTOMATION_PATH,
|
|
5202
|
+
var groupSchema, automationConfigSchema, recordSchema2, AUTOMATION_PATH, configPath2;
|
|
4950
5203
|
var init_install = __esm({
|
|
4951
5204
|
"src/automation/install.ts"() {
|
|
4952
5205
|
"use strict";
|
|
@@ -4956,11 +5209,11 @@ var init_install = __esm({
|
|
|
4956
5209
|
init_store();
|
|
4957
5210
|
init_files2();
|
|
4958
5211
|
init_ownership();
|
|
4959
|
-
groupSchema =
|
|
4960
|
-
automationConfigSchema =
|
|
4961
|
-
recordSchema2 =
|
|
5212
|
+
groupSchema = z19.object({ hooks: z19.array(z19.object({ type: z19.string(), command: z19.string().optional() }).passthrough()) }).passthrough();
|
|
5213
|
+
automationConfigSchema = z19.object({ hooks: z19.record(z19.array(groupSchema)).optional() }).passthrough();
|
|
5214
|
+
recordSchema2 = z19.object({ version: z19.literal(1), hosts: z19.record(z19.object({ command: z19.string() })) });
|
|
4962
5215
|
AUTOMATION_PATH = ".mason/local/automation.json";
|
|
4963
|
-
|
|
5216
|
+
configPath2 = (host) => host === "claude" ? ".claude/settings.json" : ".codex/hooks.json";
|
|
4964
5217
|
}
|
|
4965
5218
|
});
|
|
4966
5219
|
|
|
@@ -4971,11 +5224,11 @@ __export(teardown_exports, {
|
|
|
4971
5224
|
teardownProject: () => teardownProject
|
|
4972
5225
|
});
|
|
4973
5226
|
import { isDeepStrictEqual as isDeepStrictEqual2 } from "util";
|
|
4974
|
-
import
|
|
5227
|
+
import fs21 from "fs/promises";
|
|
4975
5228
|
import path26 from "path";
|
|
4976
5229
|
import { parse as parse3, stringify as stringify2 } from "smol-toml";
|
|
4977
5230
|
import fg8 from "fast-glob";
|
|
4978
|
-
import { z as
|
|
5231
|
+
import { z as z20 } from "zod";
|
|
4979
5232
|
async function planTeardown(root, selected) {
|
|
4980
5233
|
const edits = [], diagnostics = [];
|
|
4981
5234
|
const inputs = /* @__PURE__ */ new Map();
|
|
@@ -4994,7 +5247,7 @@ async function planTeardown(root, selected) {
|
|
|
4994
5247
|
const ownership = ownershipBefore === null ? { version: 1, files: {} } : ownershipSchema.parse(JSON.parse(ownershipBefore));
|
|
4995
5248
|
const setupBefore = await read(SETUP_PATH);
|
|
4996
5249
|
const setup = setupBefore === null ? null : setupSchema.parse(JSON.parse(setupBefore));
|
|
4997
|
-
const linked = (await workspace(root)).gitDir !== await
|
|
5250
|
+
const linked = (await workspace(root)).gitDir !== await fs21.realpath(
|
|
4998
5251
|
path26.resolve(root, (await git3(root, "rev-parse", "--git-common-dir")).trim())
|
|
4999
5252
|
);
|
|
5000
5253
|
const inactiveBefore = linked && !setup ? await read(INACTIVE_HOSTS_PATH) : null;
|
|
@@ -5003,16 +5256,16 @@ async function planTeardown(root, selected) {
|
|
|
5003
5256
|
const automation = automationBefore === null ? null : recordSchema2.parse(JSON.parse(automationBefore));
|
|
5004
5257
|
const commands2 = (host) => new Set([
|
|
5005
5258
|
automation?.hosts[host]?.command,
|
|
5006
|
-
...ownership.files[
|
|
5259
|
+
...ownership.files[configPath2(host)]?.hookCommands ?? [],
|
|
5007
5260
|
...knownManagedHookCommands(host),
|
|
5008
5261
|
hookConfig(host, hookCommand(host)).hooks.SessionStart[0].hooks[0].command,
|
|
5009
5262
|
hookConfig(host).hooks.SessionStart[0].hooks[0].command
|
|
5010
5263
|
].filter((command2) => !!command2));
|
|
5011
5264
|
const possibleHook = (command2, host) => commands2(host).has(command2) || /\bmason(?:-auto|-hook)?\b/.test(command2);
|
|
5012
|
-
const add = (file, before, after,
|
|
5013
|
-
edits.push({ path: file, before, after: ownership.files[file]?.created && (
|
|
5265
|
+
const add = (file, before, after, empty2) => {
|
|
5266
|
+
edits.push({ path: file, before, after: ownership.files[file]?.created && (empty2 || ownership.files[file].initialHash === hash(before)) ? null : after });
|
|
5014
5267
|
};
|
|
5015
|
-
let remainingHost = hosts.some((host) => !selected.includes(host) && (setup?.hosts[host] || automation?.hosts[host] || ownership.files[mcpPath(host)] || ownership.files[
|
|
5268
|
+
let remainingHost = hosts.some((host) => !selected.includes(host) && (setup?.hosts[host] || automation?.hosts[host] || ownership.files[mcpPath(host)] || ownership.files[configPath2(host)]));
|
|
5016
5269
|
for (const host of hosts.filter((host2) => !selected.includes(host2))) {
|
|
5017
5270
|
await attempt(mcpPath(host), async () => {
|
|
5018
5271
|
const text2 = await read(mcpPath(host));
|
|
@@ -5020,8 +5273,8 @@ async function planTeardown(root, selected) {
|
|
|
5020
5273
|
const servers = object2.parse((host === "codex" ? config.mcp_servers : config.mcpServers) ?? {});
|
|
5021
5274
|
remainingHost ||= servers.mason !== void 0;
|
|
5022
5275
|
});
|
|
5023
|
-
await attempt(
|
|
5024
|
-
const config = automationConfigSchema.parse(JSON.parse(await read(
|
|
5276
|
+
await attempt(configPath2(host), async () => {
|
|
5277
|
+
const config = automationConfigSchema.parse(JSON.parse(await read(configPath2(host)) ?? "{}"));
|
|
5025
5278
|
remainingHost ||= Object.values(config.hooks ?? {}).some((groups) => groups.some((group) => group.hooks.some((handler) => handler.command && possibleHook(handler.command, host))));
|
|
5026
5279
|
});
|
|
5027
5280
|
}
|
|
@@ -5063,7 +5316,7 @@ async function planTeardown(root, selected) {
|
|
|
5063
5316
|
if (!isDeepStrictEqual2(parsed, desired)) throw new Error("Removing the marked block would change other TOML settings; retained for manual cleanup.");
|
|
5064
5317
|
add(mcpFile, before, after, !after.trim());
|
|
5065
5318
|
});
|
|
5066
|
-
const hookFile =
|
|
5319
|
+
const hookFile = configPath2(host);
|
|
5067
5320
|
await attempt(hookFile, async () => {
|
|
5068
5321
|
const before = await read(hookFile);
|
|
5069
5322
|
if (before === null) return;
|
|
@@ -5131,7 +5384,7 @@ async function planTeardown(root, selected) {
|
|
|
5131
5384
|
if (!remainingHost) await attempt(".mason/local/project.json", async () => {
|
|
5132
5385
|
const before = await read(".mason/local/project.json");
|
|
5133
5386
|
if (before === null) return;
|
|
5134
|
-
|
|
5387
|
+
z20.object({ version: z20.literal(1), initializedAt: z20.string() }).strict().parse(JSON.parse(before));
|
|
5135
5388
|
cleanup.push({ path: ".mason/local/project.json", before, after: null });
|
|
5136
5389
|
});
|
|
5137
5390
|
if (!diagnostics.length) {
|
|
@@ -5139,7 +5392,7 @@ async function planTeardown(root, selected) {
|
|
|
5139
5392
|
if (setup) delete setup.hosts[host];
|
|
5140
5393
|
if (automation) delete automation.hosts[host];
|
|
5141
5394
|
delete ownership.files[mcpPath(host)];
|
|
5142
|
-
delete ownership.files[
|
|
5395
|
+
delete ownership.files[configPath2(host)];
|
|
5143
5396
|
}
|
|
5144
5397
|
if (!remainingHost) for (const file of DOC_CANDIDATES) delete ownership.files[file];
|
|
5145
5398
|
if (setupBefore !== null) cleanup.push({
|
|
@@ -5212,7 +5465,7 @@ var init_teardown = __esm({
|
|
|
5212
5465
|
init_model();
|
|
5213
5466
|
init_ownership();
|
|
5214
5467
|
hosts = ["codex", "claude"];
|
|
5215
|
-
object2 =
|
|
5468
|
+
object2 = z20.record(z20.unknown());
|
|
5216
5469
|
json = (value) => JSON.stringify(value, null, 2) + "\n";
|
|
5217
5470
|
}
|
|
5218
5471
|
});
|
|
@@ -5339,7 +5592,7 @@ var init_types2 = __esm({
|
|
|
5339
5592
|
});
|
|
5340
5593
|
|
|
5341
5594
|
// src/review/evidence/vitest.ts
|
|
5342
|
-
import { z as
|
|
5595
|
+
import { z as z21 } from "zod";
|
|
5343
5596
|
function parseVitest(raw, sourceRoot) {
|
|
5344
5597
|
const report = schema.parse(raw);
|
|
5345
5598
|
const findings = [], diagnostics = [];
|
|
@@ -5388,24 +5641,24 @@ var init_vitest = __esm({
|
|
|
5388
5641
|
"use strict";
|
|
5389
5642
|
init_paths2();
|
|
5390
5643
|
init_types2();
|
|
5391
|
-
count3 =
|
|
5392
|
-
schema =
|
|
5393
|
-
success:
|
|
5644
|
+
count3 = z21.number().int().nonnegative();
|
|
5645
|
+
schema = z21.object({
|
|
5646
|
+
success: z21.boolean(),
|
|
5394
5647
|
numTotalTests: count3,
|
|
5395
5648
|
numPassedTests: count3,
|
|
5396
5649
|
numFailedTests: count3,
|
|
5397
5650
|
numPendingTests: count3,
|
|
5398
5651
|
numTodoTests: count3,
|
|
5399
5652
|
numFailedTestSuites: count3,
|
|
5400
|
-
testResults:
|
|
5401
|
-
name:
|
|
5402
|
-
status:
|
|
5403
|
-
message:
|
|
5404
|
-
assertionResults:
|
|
5405
|
-
fullName:
|
|
5406
|
-
status:
|
|
5407
|
-
failureMessages:
|
|
5408
|
-
location:
|
|
5653
|
+
testResults: z21.array(z21.object({
|
|
5654
|
+
name: z21.string().min(1),
|
|
5655
|
+
status: z21.enum(["passed", "failed"]),
|
|
5656
|
+
message: z21.string().optional(),
|
|
5657
|
+
assertionResults: z21.array(z21.object({
|
|
5658
|
+
fullName: z21.string(),
|
|
5659
|
+
status: z21.enum(["passed", "failed", "pending", "skipped", "todo", "disabled"]),
|
|
5660
|
+
failureMessages: z21.array(z21.string()).nullable().optional(),
|
|
5661
|
+
location: z21.object({ line: count3, column: count3 }).nullable().optional()
|
|
5409
5662
|
}))
|
|
5410
5663
|
}))
|
|
5411
5664
|
});
|
|
@@ -5413,7 +5666,7 @@ var init_vitest = __esm({
|
|
|
5413
5666
|
});
|
|
5414
5667
|
|
|
5415
5668
|
// src/review/evidence/sarif.ts
|
|
5416
|
-
import { z as
|
|
5669
|
+
import { z as z22 } from "zod";
|
|
5417
5670
|
function parseSarif(raw, sourceRoot) {
|
|
5418
5671
|
const report = schema2.parse(raw), findings = [], diagnostics = [];
|
|
5419
5672
|
let active = 0, suppressed = 0, absent = 0, unresolved = 0;
|
|
@@ -5504,29 +5757,29 @@ var init_sarif = __esm({
|
|
|
5504
5757
|
"use strict";
|
|
5505
5758
|
init_paths2();
|
|
5506
5759
|
init_types2();
|
|
5507
|
-
index =
|
|
5508
|
-
artifact =
|
|
5509
|
-
message =
|
|
5510
|
-
level =
|
|
5511
|
-
location =
|
|
5512
|
-
schema2 =
|
|
5513
|
-
version:
|
|
5514
|
-
runs:
|
|
5515
|
-
tool:
|
|
5516
|
-
invocations:
|
|
5517
|
-
versionControlProvenance:
|
|
5518
|
-
originalUriBaseIds:
|
|
5519
|
-
artifacts:
|
|
5520
|
-
results:
|
|
5521
|
-
ruleId:
|
|
5760
|
+
index = z22.number().int().nonnegative();
|
|
5761
|
+
artifact = z22.object({ uri: z22.string().optional(), uriBaseId: z22.string().optional(), index: index.optional() });
|
|
5762
|
+
message = z22.object({ text: z22.string().optional(), markdown: z22.string().optional(), id: z22.string().optional(), arguments: z22.array(z22.string()).optional() });
|
|
5763
|
+
level = z22.enum(["error", "warning", "note", "none"]);
|
|
5764
|
+
location = z22.object({ physicalLocation: z22.object({ artifactLocation: artifact.optional(), region: z22.object({ startLine: index.optional(), startColumn: index.optional() }).optional() }).optional() });
|
|
5765
|
+
schema2 = z22.object({
|
|
5766
|
+
version: z22.literal("2.1.0"),
|
|
5767
|
+
runs: z22.array(z22.object({
|
|
5768
|
+
tool: z22.object({ driver: z22.object({ name: z22.string().min(1), rules: z22.array(z22.object({ id: z22.string(), defaultConfiguration: z22.object({ level: level.optional() }).optional(), messageStrings: z22.record(message).optional() })).optional(), globalMessageStrings: z22.record(message).optional() }) }),
|
|
5769
|
+
invocations: z22.array(z22.object({ executionSuccessful: z22.boolean(), commandLine: z22.string().optional(), toolExecutionNotifications: z22.array(z22.object({ level: level.optional(), message })).optional() })).optional(),
|
|
5770
|
+
versionControlProvenance: z22.array(z22.object({ revisionId: z22.string().optional() })).optional(),
|
|
5771
|
+
originalUriBaseIds: z22.record(artifact).optional(),
|
|
5772
|
+
artifacts: z22.array(z22.object({ location: artifact.optional() })).optional(),
|
|
5773
|
+
results: z22.array(z22.object({
|
|
5774
|
+
ruleId: z22.string().optional(),
|
|
5522
5775
|
ruleIndex: index.optional(),
|
|
5523
5776
|
message,
|
|
5524
5777
|
level: level.optional(),
|
|
5525
|
-
kind:
|
|
5526
|
-
baselineState:
|
|
5527
|
-
suppressions:
|
|
5528
|
-
locations:
|
|
5529
|
-
relatedLocations:
|
|
5778
|
+
kind: z22.enum(["fail", "pass", "open", "informational", "notApplicable", "review"]).optional(),
|
|
5779
|
+
baselineState: z22.enum(["new", "unchanged", "updated", "absent"]).optional(),
|
|
5780
|
+
suppressions: z22.array(z22.object({ kind: z22.enum(["inSource", "external"]), status: z22.enum(["accepted", "underReview", "rejected"]).optional() })).nullable().optional(),
|
|
5781
|
+
locations: z22.array(location).optional(),
|
|
5782
|
+
relatedLocations: z22.array(location).optional()
|
|
5530
5783
|
})).optional()
|
|
5531
5784
|
}))
|
|
5532
5785
|
});
|
|
@@ -5534,7 +5787,7 @@ var init_sarif = __esm({
|
|
|
5534
5787
|
});
|
|
5535
5788
|
|
|
5536
5789
|
// src/review/evidence/check.ts
|
|
5537
|
-
import { z as
|
|
5790
|
+
import { z as z23 } from "zod";
|
|
5538
5791
|
function parseCheck(raw, sourceRoot) {
|
|
5539
5792
|
const report = schema3.parse(raw);
|
|
5540
5793
|
const diagnostics = [], findings = [];
|
|
@@ -5574,17 +5827,17 @@ var init_check = __esm({
|
|
|
5574
5827
|
"use strict";
|
|
5575
5828
|
init_paths2();
|
|
5576
5829
|
init_types2();
|
|
5577
|
-
schema3 =
|
|
5578
|
-
version:
|
|
5579
|
-
commit:
|
|
5580
|
-
results:
|
|
5581
|
-
name:
|
|
5582
|
-
status:
|
|
5583
|
-
message:
|
|
5584
|
-
locations:
|
|
5585
|
-
file:
|
|
5586
|
-
line:
|
|
5587
|
-
column:
|
|
5830
|
+
schema3 = z23.object({
|
|
5831
|
+
version: z23.literal(1),
|
|
5832
|
+
commit: z23.string().regex(/^(?:[a-fA-F0-9]{40}|[a-fA-F0-9]{64})$/).optional(),
|
|
5833
|
+
results: z23.array(z23.object({
|
|
5834
|
+
name: z23.string().min(1).max(1e3),
|
|
5835
|
+
status: z23.enum(["passed", "failed", "skipped"]),
|
|
5836
|
+
message: z23.string().max(1e5).optional(),
|
|
5837
|
+
locations: z23.array(z23.object({
|
|
5838
|
+
file: z23.string().min(1).max(4e3),
|
|
5839
|
+
line: z23.number().int().positive().optional(),
|
|
5840
|
+
column: z23.number().int().positive().optional()
|
|
5588
5841
|
})).max(100).default([])
|
|
5589
5842
|
})).max(1e5)
|
|
5590
5843
|
});
|
|
@@ -5593,7 +5846,7 @@ var init_check = __esm({
|
|
|
5593
5846
|
|
|
5594
5847
|
// src/review/evidence.ts
|
|
5595
5848
|
import path27 from "path";
|
|
5596
|
-
import { z as
|
|
5849
|
+
import { z as z24 } from "zod";
|
|
5597
5850
|
function relativeArtifact(root, file) {
|
|
5598
5851
|
const relative = path27.isAbsolute(file) ? path27.relative(root, file) : file;
|
|
5599
5852
|
const normalized = normalizeRepoPath(relative);
|
|
@@ -5634,7 +5887,7 @@ async function collectReviewEvidence(root, manifests, changedFiles, decisions, d
|
|
|
5634
5887
|
let manifest, raw;
|
|
5635
5888
|
try {
|
|
5636
5889
|
manifest = relativeArtifact(root, supplied);
|
|
5637
|
-
raw =
|
|
5890
|
+
raw = z24.object({ version: z24.literal(1), checks: z24.array(z24.unknown()) }).parse(await readStoreJson(root, manifest));
|
|
5638
5891
|
} catch (error) {
|
|
5639
5892
|
output.diagnostics.push(`${supplied}: ${String(error)}`);
|
|
5640
5893
|
continue;
|
|
@@ -5789,31 +6042,31 @@ var init_evidence2 = __esm({
|
|
|
5789
6042
|
init_sarif();
|
|
5790
6043
|
init_check();
|
|
5791
6044
|
init_types2();
|
|
5792
|
-
checkSchema2 =
|
|
5793
|
-
id:
|
|
5794
|
-
kind:
|
|
5795
|
-
tool:
|
|
5796
|
-
command:
|
|
5797
|
-
commit:
|
|
5798
|
-
workingTreeClean:
|
|
5799
|
-
source:
|
|
5800
|
-
sourceRoot:
|
|
5801
|
-
status:
|
|
5802
|
-
reason:
|
|
5803
|
-
exitCode:
|
|
5804
|
-
environment:
|
|
5805
|
-
toolVersion:
|
|
5806
|
-
runtime:
|
|
5807
|
-
platform:
|
|
5808
|
-
configuration:
|
|
6045
|
+
checkSchema2 = z24.object({
|
|
6046
|
+
id: z24.string().min(1).max(100),
|
|
6047
|
+
kind: z24.enum(["tests", "static-analysis", "security", "complexity", "duplication", "documentation"]),
|
|
6048
|
+
tool: z24.string().min(1).max(200),
|
|
6049
|
+
command: z24.string().min(1).max(2e3),
|
|
6050
|
+
commit: z24.string().regex(/^(?:[a-fA-F0-9]{40}|[a-fA-F0-9]{64})$/).nullable().optional(),
|
|
6051
|
+
workingTreeClean: z24.boolean().optional(),
|
|
6052
|
+
source: z24.string().max(2e3).optional(),
|
|
6053
|
+
sourceRoot: z24.string().min(1).max(4e3).optional(),
|
|
6054
|
+
status: z24.enum(["completed", "skipped", "unavailable"]).default("completed"),
|
|
6055
|
+
reason: z24.string().min(1).max(2e3).optional(),
|
|
6056
|
+
exitCode: z24.number().int().nullable().optional(),
|
|
6057
|
+
environment: z24.object({
|
|
6058
|
+
toolVersion: z24.string().min(1).max(200),
|
|
6059
|
+
runtime: z24.string().min(1).max(200).optional(),
|
|
6060
|
+
platform: z24.string().min(1).max(200).optional(),
|
|
6061
|
+
configuration: z24.array(z24.string().min(1).max(4e3)).max(100).optional()
|
|
5809
6062
|
}).optional(),
|
|
5810
|
-
report:
|
|
6063
|
+
report: z24.object({ format: z24.enum(["vitest-json", "sarif", "mason-check-json"]), path: z24.string().min(1).max(4e3) }).optional()
|
|
5811
6064
|
});
|
|
5812
6065
|
}
|
|
5813
6066
|
});
|
|
5814
6067
|
|
|
5815
6068
|
// src/review/review.ts
|
|
5816
|
-
import
|
|
6069
|
+
import fs22 from "fs/promises";
|
|
5817
6070
|
import path28 from "path";
|
|
5818
6071
|
import { execFile as execFile4 } from "child_process";
|
|
5819
6072
|
import { promisify as promisify4 } from "util";
|
|
@@ -5893,7 +6146,7 @@ async function computeReview(rootDir, base, options = {}) {
|
|
|
5893
6146
|
analyzed,
|
|
5894
6147
|
async (relPath) => {
|
|
5895
6148
|
try {
|
|
5896
|
-
await
|
|
6149
|
+
await fs22.access(path28.join(resolvedRoot, relPath));
|
|
5897
6150
|
return true;
|
|
5898
6151
|
} catch {
|
|
5899
6152
|
return false;
|
|
@@ -6048,7 +6301,7 @@ async function setupStatus(dir) {
|
|
|
6048
6301
|
version: 1,
|
|
6049
6302
|
status: pending.length ? "incomplete" : "not-configured",
|
|
6050
6303
|
hosts: {},
|
|
6051
|
-
next: pending.length ? "Setup did not finish. Rerun mason
|
|
6304
|
+
next: pending.length ? "Setup did not finish. Rerun mason setup --host " + pending[0] + " to resume using the retained original evidence." : "Project hooks are inactive until local setup. Run mason setup --host codex or --host claude."
|
|
6052
6305
|
};
|
|
6053
6306
|
}
|
|
6054
6307
|
const hosts2 = {};
|
|
@@ -6082,7 +6335,7 @@ async function setupStatus(dir) {
|
|
|
6082
6335
|
if (!mcp || !hooks || !instructionsCurrent || !configured) pending.push("Rerun setup to reconcile project configuration; inspect any disabled host settings.");
|
|
6083
6336
|
if (!observation?.contextCalls) pending.push("Start a new assistant session and request task context through Mason's get_context tool.");
|
|
6084
6337
|
if (!complete) pending.push("Review/trust the host configuration, then complete a normal task to observe the full hook lifecycle.");
|
|
6085
|
-
if (automation.status === "unavailable") pending.push("The latest automation attempt did not establish verification. Run mason
|
|
6338
|
+
if (automation.status === "unavailable") pending.push("The latest automation attempt did not establish verification. Run mason check and inspect its diagnostics.");
|
|
6086
6339
|
const healthy = installed.available && mcp && hooks && instructionsCurrent && configured && automation.status !== "unavailable";
|
|
6087
6340
|
hosts2[host] = {
|
|
6088
6341
|
status: healthy ? complete && observation?.contextCalls ? "active" : "pending" : "attention",
|
|
@@ -6216,6 +6469,11 @@ async function setupProject(dir, options = {}) {
|
|
|
6216
6469
|
options.progress?.step("Checking configuration and retained findings");
|
|
6217
6470
|
const checked = await automate(ws.root, { event: "turn_start" });
|
|
6218
6471
|
const configured = await inspectHostConfig(ws.root, host, void 0);
|
|
6472
|
+
const stats = await configureUsefulness(ws.root, true, { onlyIfUnset: true }).catch(() => ({
|
|
6473
|
+
enabled: null,
|
|
6474
|
+
storage: ".mason/local/usefulness",
|
|
6475
|
+
note: "Local stats unavailable. Inspect mason stats --json, or use mason stats --disable to reset the preference. Existing observations were retained."
|
|
6476
|
+
}));
|
|
6219
6477
|
await writeStoreJson(ws.root, receiptPath, {
|
|
6220
6478
|
version: 1,
|
|
6221
6479
|
host,
|
|
@@ -6238,6 +6496,7 @@ async function setupProject(dir, options = {}) {
|
|
|
6238
6496
|
findings,
|
|
6239
6497
|
reportPath: checked.report.reportPath,
|
|
6240
6498
|
activation: await setupStatus(ws.root),
|
|
6499
|
+
stats,
|
|
6241
6500
|
next: configured.disabled || configured.mcpDisabled ? "Mason hooks or MCP are disabled in project configuration. Review that setting before activation." : host === "codex" ? "Review/trust this project's MCP configuration and hooks in Codex (/hooks in the CLI), then start a new session and give it a normal task." : "Approve the project MCP server in Claude Code, then start a new session and give it a normal task."
|
|
6242
6501
|
};
|
|
6243
6502
|
});
|
|
@@ -6249,18 +6508,20 @@ function summarizeSetup(result) {
|
|
|
6249
6508
|
" MCP server and lifecycle hooks use mason from PATH.",
|
|
6250
6509
|
" Project instructions updated; original audit evidence retained.",
|
|
6251
6510
|
` ${result.changedFiles.length} files changed.`,
|
|
6511
|
+
result.stats.enabled === null ? result.stats.note : result.stats.enabled ? "Local stats enabled: metadata stays in .mason/local/usefulness/; nothing is uploaded. View with mason stats; disable with mason stats --disable." : "Local stats remain disabled by your saved preference. Enable with mason stats --enable.",
|
|
6252
6512
|
`Initial audit: ${audit.status}${"counts" in audit ? `; ${audit.counts.issues} issues, ${audit.counts.advisories + audit.counts.suppressedAdvisories} advisories` : ""}.`,
|
|
6253
6513
|
..."issues" in audit ? audit.issues.slice(0, 3).map((f) => " " + f.message) : [],
|
|
6254
6514
|
`Original evidence: ${result.initialReportPath}`,
|
|
6255
6515
|
"Activation: " + result.activation.status + ".",
|
|
6256
6516
|
result.next,
|
|
6257
6517
|
"If the assistant cannot find mason, restart the desktop app so it picks up PATH.",
|
|
6258
|
-
"After the task finishes, run mason
|
|
6518
|
+
"After the task finishes, run mason status. Configuration alone does not establish activation."
|
|
6259
6519
|
].join("\n");
|
|
6260
6520
|
}
|
|
6261
6521
|
var init_setup = __esm({
|
|
6262
6522
|
"src/setup/setup.ts"() {
|
|
6263
6523
|
"use strict";
|
|
6524
|
+
init_usefulness();
|
|
6264
6525
|
init_evidence();
|
|
6265
6526
|
init_store();
|
|
6266
6527
|
init_runtime();
|
|
@@ -6277,13 +6538,13 @@ var init_setup = __esm({
|
|
|
6277
6538
|
});
|
|
6278
6539
|
|
|
6279
6540
|
// src/distribution/path.ts
|
|
6280
|
-
import
|
|
6541
|
+
import fs23 from "fs/promises";
|
|
6281
6542
|
import os3 from "os";
|
|
6282
6543
|
import path29 from "path";
|
|
6283
6544
|
import { execFile as execFile5 } from "child_process";
|
|
6284
6545
|
import { promisify as promisify5 } from "util";
|
|
6285
6546
|
import { createHash as createHash4, randomUUID as randomUUID7 } from "crypto";
|
|
6286
|
-
import { z as
|
|
6547
|
+
import { z as z25 } from "zod";
|
|
6287
6548
|
async function configurePath(bin, previous, save, options = {}) {
|
|
6288
6549
|
const env = options.env ?? process.env, platform = options.platform ?? process.platform;
|
|
6289
6550
|
const changes = [...previous];
|
|
@@ -6318,7 +6579,7 @@ ${alreadyOnPath ? "" : `Add ${bin} to your PATH.
|
|
|
6318
6579
|
else {
|
|
6319
6580
|
const candidates = [".bash_profile", ".bash_login", ".profile"].map((name) => path29.join(userHome, name));
|
|
6320
6581
|
let login = candidates[2];
|
|
6321
|
-
for (const file of candidates) if (await
|
|
6582
|
+
for (const file of candidates) if (await fs23.lstat(file).then(() => true, (error) => {
|
|
6322
6583
|
if (error.code === "ENOENT") return false;
|
|
6323
6584
|
throw error;
|
|
6324
6585
|
})) {
|
|
@@ -6329,12 +6590,12 @@ ${alreadyOnPath ? "" : `Add ${bin} to your PATH.
|
|
|
6329
6590
|
}
|
|
6330
6591
|
const marker = `# >>> mason PATH ${createHash4("sha256").update(bin).digest("hex").slice(0, 12)} >>>`;
|
|
6331
6592
|
for (const profile of profiles2) {
|
|
6332
|
-
const stat = await
|
|
6593
|
+
const stat = await fs23.lstat(profile).catch((error) => {
|
|
6333
6594
|
if (error.code === "ENOENT") return null;
|
|
6334
6595
|
throw error;
|
|
6335
6596
|
});
|
|
6336
|
-
const file = stat?.isSymbolicLink() ? await
|
|
6337
|
-
const original = await
|
|
6597
|
+
const file = stat?.isSymbolicLink() ? await fs23.realpath(profile) : path29.resolve(profile);
|
|
6598
|
+
const original = await fs23.readFile(file).catch((error) => {
|
|
6338
6599
|
if (error.code === "ENOENT" && !stat) return null;
|
|
6339
6600
|
throw error;
|
|
6340
6601
|
});
|
|
@@ -6349,8 +6610,8 @@ ${alreadyOnPath ? "" : `Add ${bin} to your PATH.
|
|
|
6349
6610
|
changes.push({ kind: "profile", file, block, created: original === null });
|
|
6350
6611
|
await save(changes);
|
|
6351
6612
|
}
|
|
6352
|
-
await
|
|
6353
|
-
const handle = await
|
|
6613
|
+
await fs23.mkdir(path29.dirname(file), { recursive: true });
|
|
6614
|
+
const handle = await fs23.open(file, original === null ? "ax+" : "a+", 384);
|
|
6354
6615
|
try {
|
|
6355
6616
|
if (!(await handle.readFile()).equals(original ?? Buffer.alloc(0))) throw new Error(`Shell profile changed during installation: ${profile}`);
|
|
6356
6617
|
await handle.writeFile(block);
|
|
@@ -6374,13 +6635,13 @@ async function removePath(changes) {
|
|
|
6374
6635
|
await windowsPath("remove", change.entry, current, change.created);
|
|
6375
6636
|
continue;
|
|
6376
6637
|
}
|
|
6377
|
-
const stat = await
|
|
6638
|
+
const stat = await fs23.lstat(change.file).catch((error) => {
|
|
6378
6639
|
if (error.code === "ENOENT") return null;
|
|
6379
6640
|
throw error;
|
|
6380
6641
|
});
|
|
6381
6642
|
if (!stat) continue;
|
|
6382
6643
|
if (!stat.isFile()) throw new Error("profile type changed");
|
|
6383
|
-
const bytes = await
|
|
6644
|
+
const bytes = await fs23.readFile(change.file), text2 = bytes.toString("utf8");
|
|
6384
6645
|
if (!Buffer.from(text2).equals(bytes)) throw new Error("profile encoding changed");
|
|
6385
6646
|
const index2 = text2.indexOf(change.block);
|
|
6386
6647
|
if (index2 < 0 || text2.indexOf(change.block, index2 + change.block.length) >= 0) throw new Error("PATH block was edited or removed");
|
|
@@ -6390,7 +6651,7 @@ async function removePath(changes) {
|
|
|
6390
6651
|
const temporary = change.file + ".mason-" + randomUUID7();
|
|
6391
6652
|
try {
|
|
6392
6653
|
if (stat.nlink > 1) throw new Error("profile has multiple hard links");
|
|
6393
|
-
const handle = await
|
|
6654
|
+
const handle = await fs23.open(temporary, "wx", stat.mode & 511);
|
|
6394
6655
|
try {
|
|
6395
6656
|
await handle.writeFile(next);
|
|
6396
6657
|
await handle.chmod(stat.mode & 511);
|
|
@@ -6399,11 +6660,11 @@ async function removePath(changes) {
|
|
|
6399
6660
|
} finally {
|
|
6400
6661
|
await handle.close();
|
|
6401
6662
|
}
|
|
6402
|
-
const current = await
|
|
6403
|
-
if (!current.isFile() || current.ino !== stat.ino || !(await
|
|
6404
|
-
await
|
|
6663
|
+
const current = await fs23.lstat(change.file);
|
|
6664
|
+
if (!current.isFile() || current.ino !== stat.ino || !(await fs23.readFile(change.file)).equals(bytes)) throw new Error("profile changed during uninstall");
|
|
6665
|
+
await fs23.rename(temporary, change.file);
|
|
6405
6666
|
} finally {
|
|
6406
|
-
await
|
|
6667
|
+
await fs23.rm(temporary, { force: true });
|
|
6407
6668
|
}
|
|
6408
6669
|
} catch (error) {
|
|
6409
6670
|
warnings.push(`Retained PATH setting ${change.kind === "profile" ? change.file : change.entry}: ${error instanceof Error ? error.message : String(error)}`);
|
|
@@ -6445,9 +6706,9 @@ var init_path = __esm({
|
|
|
6445
6706
|
"src/distribution/path.ts"() {
|
|
6446
6707
|
"use strict";
|
|
6447
6708
|
execute2 = promisify5(execFile5);
|
|
6448
|
-
pathChangeSchema =
|
|
6449
|
-
|
|
6450
|
-
|
|
6709
|
+
pathChangeSchema = z25.discriminatedUnion("kind", [
|
|
6710
|
+
z25.object({ kind: z25.literal("profile"), file: z25.string(), block: z25.string(), created: z25.boolean() }),
|
|
6711
|
+
z25.object({ kind: z25.literal("windows"), entry: z25.string(), created: z25.boolean() })
|
|
6451
6712
|
]);
|
|
6452
6713
|
quote = (s) => "'" + s.replace(/'/g, "'\\''") + "'";
|
|
6453
6714
|
normalizeWindowsEntry = (value) => path29.win32.normalize(value.trim().replace(/^"(.*)"$/, "$1")).replace(/[\\/]+$/, "").toLowerCase();
|
|
@@ -6475,13 +6736,13 @@ __export(state_exports, {
|
|
|
6475
6736
|
import os4 from "os";
|
|
6476
6737
|
import path30 from "path";
|
|
6477
6738
|
import { fileURLToPath } from "url";
|
|
6478
|
-
import
|
|
6479
|
-
import { z as
|
|
6739
|
+
import fs24 from "fs/promises";
|
|
6740
|
+
import { z as z26 } from "zod";
|
|
6480
6741
|
async function readInstallation(home) {
|
|
6481
6742
|
const raw = await readStoreJson(home, "install.json");
|
|
6482
6743
|
if (!raw) throw new Error("Run this command from a standalone installation. npm users can upgrade with npm.");
|
|
6483
6744
|
const record = recordSchema3.parse(raw);
|
|
6484
|
-
if (record.home !== await
|
|
6745
|
+
if (record.home !== await fs24.realpath(home)) throw new Error("Invalid standalone installation receipt.");
|
|
6485
6746
|
return record;
|
|
6486
6747
|
}
|
|
6487
6748
|
function standaloneLocation() {
|
|
@@ -6516,38 +6777,38 @@ var init_state = __esm({
|
|
|
6516
6777
|
"use strict";
|
|
6517
6778
|
init_storage();
|
|
6518
6779
|
init_path();
|
|
6519
|
-
releaseVersion =
|
|
6520
|
-
bundleId =
|
|
6521
|
-
updatePolicySchema =
|
|
6522
|
-
enabled:
|
|
6523
|
-
revision:
|
|
6780
|
+
releaseVersion = z26.string().regex(/^\d+\.\d+\.\d+(?:-[A-Za-z0-9.-]+)?$/);
|
|
6781
|
+
bundleId = z26.string().regex(/^[a-f0-9]{24}$/);
|
|
6782
|
+
updatePolicySchema = z26.object({
|
|
6783
|
+
enabled: z26.boolean(),
|
|
6784
|
+
revision: z26.string(),
|
|
6524
6785
|
pinnedVersion: releaseVersion.optional(),
|
|
6525
|
-
mirror:
|
|
6786
|
+
mirror: z26.string().optional()
|
|
6526
6787
|
});
|
|
6527
|
-
recordSchema3 =
|
|
6528
|
-
format:
|
|
6529
|
-
home:
|
|
6530
|
-
bin:
|
|
6788
|
+
recordSchema3 = z26.object({
|
|
6789
|
+
format: z26.literal(1),
|
|
6790
|
+
home: z26.string(),
|
|
6791
|
+
bin: z26.string(),
|
|
6531
6792
|
current: bundleId,
|
|
6532
6793
|
version: releaseVersion,
|
|
6533
|
-
launchers:
|
|
6534
|
-
previousLaunchers:
|
|
6535
|
-
pathChanges:
|
|
6794
|
+
launchers: z26.record(z26.string()),
|
|
6795
|
+
previousLaunchers: z26.record(z26.string()).optional(),
|
|
6796
|
+
pathChanges: z26.array(pathChangeSchema).optional(),
|
|
6536
6797
|
updates: updatePolicySchema.optional(),
|
|
6537
|
-
previous:
|
|
6538
|
-
pending:
|
|
6798
|
+
previous: z26.object({ id: bundleId, version: releaseVersion }).optional(),
|
|
6799
|
+
pending: z26.object({ id: bundleId, version: releaseVersion, policyRevision: z26.string() }).optional()
|
|
6539
6800
|
});
|
|
6540
6801
|
defaultHome = () => process.env.MASON_HOME ?? (process.platform === "win32" ? path30.join(process.env.LOCALAPPDATA ?? os4.homedir(), "Mason") : path30.join(os4.homedir(), ".local/share/mason"));
|
|
6541
6802
|
}
|
|
6542
6803
|
});
|
|
6543
6804
|
|
|
6544
6805
|
// src/distribution/bundle.ts
|
|
6545
|
-
import
|
|
6806
|
+
import fs25 from "fs/promises";
|
|
6546
6807
|
import path31 from "path";
|
|
6547
6808
|
import { createHash as createHash5 } from "crypto";
|
|
6548
|
-
import { z as
|
|
6809
|
+
import { z as z27 } from "zod";
|
|
6549
6810
|
async function readBundle(root) {
|
|
6550
|
-
const bytes = await
|
|
6811
|
+
const bytes = await fs25.readFile(await storePath(root, "bundle.json"));
|
|
6551
6812
|
const manifest = bundleSchema.parse(JSON.parse(bytes.toString("utf8")));
|
|
6552
6813
|
const windows = manifest.target.startsWith("win32-");
|
|
6553
6814
|
for (const file of Object.keys(manifest.files)) {
|
|
@@ -6555,7 +6816,7 @@ async function readBundle(root) {
|
|
|
6555
6816
|
throw new Error("Unsafe path in Mason bundle: " + file);
|
|
6556
6817
|
}
|
|
6557
6818
|
}
|
|
6558
|
-
for (const required of [windows ? "node.exe" : "node", "app/package.json", "app/dist/mason.js", "app/dist/mason-
|
|
6819
|
+
for (const required of [windows ? "node.exe" : "node", "app/package.json", "app/dist/mason.js", "app/dist/mason-mcp.js"]) {
|
|
6559
6820
|
if (!manifest.files[required]) throw new Error("Incomplete Mason bundle: " + required);
|
|
6560
6821
|
}
|
|
6561
6822
|
return { manifest, manifestHash: sha256(bytes) };
|
|
@@ -6566,7 +6827,7 @@ async function verifyBundle(root, expectedHash) {
|
|
|
6566
6827
|
if (bundle.manifest.target !== `${process.platform}-${process.arch}`) throw new Error("Mason bundle is for another platform. Install this platform's bundle and rerun setup.");
|
|
6567
6828
|
const files = Object.keys(bundle.manifest.files);
|
|
6568
6829
|
for (let i = 0; i < files.length; i += 8) await Promise.all(files.slice(i, i + 8).map(async (file) => {
|
|
6569
|
-
if (sha256(await
|
|
6830
|
+
if (sha256(await fs25.readFile(await storePath(root, file))) !== bundle.manifest.files[file]) throw new Error("Mason bundle checksum mismatch: " + file);
|
|
6570
6831
|
}));
|
|
6571
6832
|
return bundle;
|
|
6572
6833
|
}
|
|
@@ -6576,13 +6837,13 @@ var init_bundle = __esm({
|
|
|
6576
6837
|
"use strict";
|
|
6577
6838
|
init_storage();
|
|
6578
6839
|
targets = ["darwin-arm64", "darwin-x64", "linux-arm64", "linux-x64", "win32-arm64", "win32-x64"];
|
|
6579
|
-
digest2 =
|
|
6580
|
-
bundleSchema =
|
|
6581
|
-
format:
|
|
6582
|
-
version:
|
|
6583
|
-
target:
|
|
6584
|
-
nodeVersion:
|
|
6585
|
-
files:
|
|
6840
|
+
digest2 = z27.string().regex(/^[a-f0-9]{64}$/);
|
|
6841
|
+
bundleSchema = z27.object({
|
|
6842
|
+
format: z27.literal(1),
|
|
6843
|
+
version: z27.string().regex(/^\d+\.\d+\.\d+(?:-[A-Za-z0-9.-]+)?$/),
|
|
6844
|
+
target: z27.enum(targets),
|
|
6845
|
+
nodeVersion: z27.string(),
|
|
6846
|
+
files: z27.record(digest2)
|
|
6586
6847
|
}).strict();
|
|
6587
6848
|
sha256 = (bytes) => createHash5("sha256").update(bytes).digest("hex");
|
|
6588
6849
|
}
|
|
@@ -6605,27 +6866,27 @@ var init_trust2 = __esm({
|
|
|
6605
6866
|
});
|
|
6606
6867
|
|
|
6607
6868
|
// src/distribution/runtime.ts
|
|
6608
|
-
import
|
|
6869
|
+
import fs26 from "fs/promises";
|
|
6609
6870
|
import { unlinkSync } from "fs";
|
|
6610
6871
|
import os5 from "os";
|
|
6611
6872
|
import path32 from "path";
|
|
6612
6873
|
import { randomUUID as randomUUID8 } from "crypto";
|
|
6613
|
-
import { z as
|
|
6874
|
+
import { z as z28 } from "zod";
|
|
6614
6875
|
async function pruneUnusedVersions(home) {
|
|
6615
6876
|
const versions = await storePath(home, "versions");
|
|
6616
6877
|
const candidates = [];
|
|
6617
6878
|
const initial = await readInstallation(home);
|
|
6618
6879
|
const initialKeep = /* @__PURE__ */ new Set([initial.current, initial.previous?.id, initial.pending?.id]);
|
|
6619
|
-
for (const name of await
|
|
6880
|
+
for (const name of await fs26.readdir(versions)) {
|
|
6620
6881
|
const retired = /^\.retired-([a-f0-9]{24})-[a-f0-9-]+$/.exec(name);
|
|
6621
6882
|
const id = retired?.[1] ?? name;
|
|
6622
6883
|
if (!/^[a-f0-9]{24}$/.test(id) || initialKeep.has(id)) continue;
|
|
6623
6884
|
try {
|
|
6624
|
-
const
|
|
6625
|
-
await
|
|
6626
|
-
const bundle = await verifyBundle(
|
|
6885
|
+
const directory2 = await storePath(home, `versions/${name}`);
|
|
6886
|
+
await fs26.access(await storePath(directory2, "app/dist/mason-runtime.js"));
|
|
6887
|
+
const bundle = await verifyBundle(directory2);
|
|
6627
6888
|
if (bundle.manifestHash.slice(0, 24) === id && bundle.manifest.files["app/dist/mason-runtime.js"])
|
|
6628
|
-
candidates.push({ id, directory, retired: !!retired });
|
|
6889
|
+
candidates.push({ id, directory: directory2, retired: !!retired });
|
|
6629
6890
|
} catch {
|
|
6630
6891
|
}
|
|
6631
6892
|
}
|
|
@@ -6634,8 +6895,8 @@ async function pruneUnusedVersions(home) {
|
|
|
6634
6895
|
await withLock(home, ".install-lock", async () => {
|
|
6635
6896
|
const record = await readInstallation(home);
|
|
6636
6897
|
const keep = /* @__PURE__ */ new Set([record.current, record.previous?.id, record.pending?.id]);
|
|
6637
|
-
const
|
|
6638
|
-
const leases = await
|
|
6898
|
+
const directory2 = await storePath(home, "runtimes");
|
|
6899
|
+
const leases = await fs26.readdir(directory2).catch((error) => {
|
|
6639
6900
|
if (error.code === "ENOENT") return [];
|
|
6640
6901
|
throw error;
|
|
6641
6902
|
});
|
|
@@ -6651,7 +6912,7 @@ async function pruneUnusedVersions(home) {
|
|
|
6651
6912
|
keep.add(lease.bundle);
|
|
6652
6913
|
} catch (error) {
|
|
6653
6914
|
if (error.code !== "ESRCH") return;
|
|
6654
|
-
await
|
|
6915
|
+
await fs26.rm(await storePath(home, `runtimes/${name}`), { force: true });
|
|
6655
6916
|
}
|
|
6656
6917
|
} catch {
|
|
6657
6918
|
return;
|
|
@@ -6661,13 +6922,13 @@ async function pruneUnusedVersions(home) {
|
|
|
6661
6922
|
if (keep.has(candidate.id)) continue;
|
|
6662
6923
|
try {
|
|
6663
6924
|
const retired = candidate.retired ? candidate.directory : await storePath(home, `versions/.retired-${candidate.id}-${randomUUID8()}`);
|
|
6664
|
-
if (!candidate.retired) await
|
|
6925
|
+
if (!candidate.retired) await fs26.rename(candidate.directory, retired);
|
|
6665
6926
|
garbage.push(retired);
|
|
6666
6927
|
} catch {
|
|
6667
6928
|
}
|
|
6668
6929
|
}
|
|
6669
6930
|
});
|
|
6670
|
-
for (const
|
|
6931
|
+
for (const directory2 of garbage) await fs26.rm(directory2, { recursive: true }).catch(() => {
|
|
6671
6932
|
});
|
|
6672
6933
|
}
|
|
6673
6934
|
var leaseSchema;
|
|
@@ -6679,18 +6940,18 @@ var init_runtime2 = __esm({
|
|
|
6679
6940
|
init_state();
|
|
6680
6941
|
init_bundle();
|
|
6681
6942
|
init_trust2();
|
|
6682
|
-
leaseSchema =
|
|
6943
|
+
leaseSchema = z28.object({ pid: z28.number().int().positive(), host: z28.string(), bundle: z28.string().regex(/^[a-f0-9]{24}$/) });
|
|
6683
6944
|
}
|
|
6684
6945
|
});
|
|
6685
6946
|
|
|
6686
6947
|
// src/distribution/sessions.ts
|
|
6687
|
-
import
|
|
6948
|
+
import fs27 from "fs/promises";
|
|
6688
6949
|
import os6 from "os";
|
|
6689
6950
|
import { randomUUID as randomUUID9 } from "crypto";
|
|
6690
|
-
import { z as
|
|
6951
|
+
import { z as z29 } from "zod";
|
|
6691
6952
|
async function hasRunningMcp(home) {
|
|
6692
|
-
const
|
|
6693
|
-
const files = await
|
|
6953
|
+
const directory2 = await storePath(home, "sessions");
|
|
6954
|
+
const files = await fs27.readdir(directory2).catch((error) => {
|
|
6694
6955
|
if (error.code === "ENOENT") return [];
|
|
6695
6956
|
throw error;
|
|
6696
6957
|
});
|
|
@@ -6708,7 +6969,7 @@ async function hasRunningMcp(home) {
|
|
|
6708
6969
|
} catch (error) {
|
|
6709
6970
|
if (error.code !== "ESRCH") return true;
|
|
6710
6971
|
}
|
|
6711
|
-
await
|
|
6972
|
+
await fs27.rm(await storePath(home, relative), { force: true });
|
|
6712
6973
|
} catch {
|
|
6713
6974
|
return true;
|
|
6714
6975
|
}
|
|
@@ -6719,7 +6980,7 @@ async function registerMcp(home) {
|
|
|
6719
6980
|
const name = `sessions/${randomUUID9()}.json`;
|
|
6720
6981
|
await writeStoreJson(home, name, { pid: process.pid, host: os6.hostname() });
|
|
6721
6982
|
return async () => {
|
|
6722
|
-
await
|
|
6983
|
+
await fs27.rm(await storePath(home, name), { force: true }).catch(() => {
|
|
6723
6984
|
});
|
|
6724
6985
|
};
|
|
6725
6986
|
}
|
|
@@ -6728,7 +6989,7 @@ var init_sessions = __esm({
|
|
|
6728
6989
|
"src/distribution/sessions.ts"() {
|
|
6729
6990
|
"use strict";
|
|
6730
6991
|
init_storage();
|
|
6731
|
-
leaseSchema2 =
|
|
6992
|
+
leaseSchema2 = z29.object({ pid: z29.number().int().positive(), host: z29.string() });
|
|
6732
6993
|
}
|
|
6733
6994
|
});
|
|
6734
6995
|
|
|
@@ -6743,7 +7004,7 @@ __export(install_exports2, {
|
|
|
6743
7004
|
uninstallStandalone: () => uninstallStandalone,
|
|
6744
7005
|
upgradeStandalone: () => upgradeStandalone
|
|
6745
7006
|
});
|
|
6746
|
-
import
|
|
7007
|
+
import fs28 from "fs/promises";
|
|
6747
7008
|
import os7 from "os";
|
|
6748
7009
|
import path33 from "path";
|
|
6749
7010
|
import { execFile as execFile6, spawn } from "child_process";
|
|
@@ -6752,18 +7013,18 @@ import { randomUUID as randomUUID10 } from "crypto";
|
|
|
6752
7013
|
async function replace(file, content2, mode = 384) {
|
|
6753
7014
|
const temp = file + ".tmp-" + randomUUID10();
|
|
6754
7015
|
try {
|
|
6755
|
-
await
|
|
6756
|
-
await
|
|
7016
|
+
await fs28.writeFile(temp, content2, { mode, flag: "wx" });
|
|
7017
|
+
await fs28.rename(temp, file);
|
|
6757
7018
|
} finally {
|
|
6758
|
-
await
|
|
7019
|
+
await fs28.rm(temp, { force: true });
|
|
6759
7020
|
}
|
|
6760
7021
|
}
|
|
6761
7022
|
async function installStandalone(source, progress2, options = {}) {
|
|
6762
7023
|
progress2?.step("Checking installation files and location");
|
|
6763
7024
|
const bundle = await verifyBundle(source);
|
|
6764
7025
|
const selectedHome = path33.resolve(defaultHome());
|
|
6765
|
-
if (!options.stageOnly) await
|
|
6766
|
-
const home = await
|
|
7026
|
+
if (!options.stageOnly) await fs28.mkdir(selectedHome, { recursive: true });
|
|
7027
|
+
const home = await fs28.realpath(selectedHome);
|
|
6767
7028
|
if (options.stageOnly) {
|
|
6768
7029
|
assertUpdatePolicy(await readInstallation(home), bundle.manifest.version, options.policyRevision);
|
|
6769
7030
|
const destination = await storePath(home, "versions/" + bundle.manifestHash.slice(0, 24));
|
|
@@ -6786,17 +7047,17 @@ async function installLocked2(home, source, bundle, progress2, options, alreadyS
|
|
|
6786
7047
|
const raw = await readStoreJson(home, "install.json");
|
|
6787
7048
|
const previous = raw === null ? null : recordSchema3.parse(raw);
|
|
6788
7049
|
if (options.stageOnly) assertUpdatePolicy(previous, bundle.manifest.version, options.policyRevision);
|
|
6789
|
-
if (!previous) for (const file of await
|
|
7050
|
+
if (!previous) for (const file of await fs28.readdir(home)) {
|
|
6790
7051
|
if (file === ".install-lock") continue;
|
|
6791
|
-
const
|
|
6792
|
-
const stat = await
|
|
6793
|
-
if (!["versions", "bin"].includes(file) || !stat.isDirectory() || (await
|
|
7052
|
+
const directory2 = path33.join(home, file);
|
|
7053
|
+
const stat = await fs28.lstat(directory2);
|
|
7054
|
+
if (!["versions", "bin"].includes(file) || !stat.isDirectory() || (await fs28.readdir(directory2)).length) {
|
|
6794
7055
|
throw new Error("Mason installation directory is not empty and has no ownership receipt.");
|
|
6795
7056
|
}
|
|
6796
7057
|
}
|
|
6797
7058
|
const selectedBin = path33.resolve(process.env.MASON_BIN_DIR ?? previous?.bin ?? (process.platform === "win32" ? path33.join(home, "bin") : path33.join(os7.homedir(), ".local/bin")));
|
|
6798
|
-
await
|
|
6799
|
-
const bin = await
|
|
7059
|
+
await fs28.mkdir(selectedBin, { recursive: true });
|
|
7060
|
+
const bin = await fs28.realpath(selectedBin);
|
|
6800
7061
|
if (previous && (previous.home !== home || previous.bin !== bin)) throw new Error("Installation paths differ from the existing Mason receipt.");
|
|
6801
7062
|
const id = bundle.manifestHash.slice(0, 24);
|
|
6802
7063
|
const destination = await storePath(home, "versions/" + id);
|
|
@@ -6828,7 +7089,7 @@ exec ${shQuote(path33.join(destination, "node"))} ${shQuote(path33.join(destinat
|
|
|
6828
7089
|
` };
|
|
6829
7090
|
for (const [name, content2] of Object.entries(launchers)) {
|
|
6830
7091
|
const file = await storePath(bin, name);
|
|
6831
|
-
const old = await
|
|
7092
|
+
const old = await fs28.readFile(file, "utf8").catch((error) => {
|
|
6832
7093
|
if (error.code === "ENOENT") return null;
|
|
6833
7094
|
throw error;
|
|
6834
7095
|
});
|
|
@@ -6837,7 +7098,7 @@ exec ${shQuote(path33.join(destination, "node"))} ${shQuote(path33.join(destinat
|
|
|
6837
7098
|
const retired = Object.entries({ ...previous?.previousLaunchers, ...previous?.launchers }).filter(([name]) => !(name in launchers));
|
|
6838
7099
|
const checkRetired = async (name, expected) => {
|
|
6839
7100
|
if (!launcherNames.includes(name)) throw new Error("Invalid owned launcher name.");
|
|
6840
|
-
const actual = await
|
|
7101
|
+
const actual = await fs28.readFile(await storePath(bin, name), "utf8").catch((error) => {
|
|
6841
7102
|
if (error.code === "ENOENT") return null;
|
|
6842
7103
|
throw error;
|
|
6843
7104
|
});
|
|
@@ -6872,7 +7133,7 @@ exec ${shQuote(path33.join(destination, "node"))} ${shQuote(path33.join(destinat
|
|
|
6872
7133
|
for (const [name, content2] of Object.entries(launchers)) await replace(await storePath(bin, name, true), content2, 493);
|
|
6873
7134
|
for (const [name, expected] of retired) {
|
|
6874
7135
|
await checkRetired(name, expected);
|
|
6875
|
-
await
|
|
7136
|
+
await fs28.rm(await storePath(bin, name), { force: true });
|
|
6876
7137
|
}
|
|
6877
7138
|
await writeStoreJson(home, "install.json", receipt);
|
|
6878
7139
|
} catch (error) {
|
|
@@ -6891,7 +7152,7 @@ exec ${shQuote(path33.join(destination, "node"))} ${shQuote(path33.join(destinat
|
|
|
6891
7152
|
return { home, bin, version: bundle.manifest.version, previousVersion: previous?.version, path: pathResult };
|
|
6892
7153
|
}
|
|
6893
7154
|
async function copyBundle(home, source, destination, bundle) {
|
|
6894
|
-
const exists = await
|
|
7155
|
+
const exists = await fs28.lstat(destination).then(() => true, (error) => {
|
|
6895
7156
|
if (error.code === "ENOENT") return false;
|
|
6896
7157
|
throw error;
|
|
6897
7158
|
});
|
|
@@ -6900,16 +7161,16 @@ async function copyBundle(home, source, destination, bundle) {
|
|
|
6900
7161
|
return;
|
|
6901
7162
|
}
|
|
6902
7163
|
const stage = await storePath(home, "versions/.install-" + randomUUID10(), true);
|
|
6903
|
-
await
|
|
7164
|
+
await fs28.mkdir(stage);
|
|
6904
7165
|
try {
|
|
6905
7166
|
for (const file of ["bundle.json", ...Object.keys(bundle.manifest.files)]) {
|
|
6906
7167
|
const from = await storePath(source, file), to = await storePath(stage, file, true);
|
|
6907
|
-
await
|
|
6908
|
-
await
|
|
7168
|
+
await fs28.copyFile(from, to);
|
|
7169
|
+
await fs28.chmod(to, (await fs28.stat(from)).mode & 511);
|
|
6909
7170
|
}
|
|
6910
7171
|
await verifyBundle(stage, bundle.manifestHash);
|
|
6911
7172
|
try {
|
|
6912
|
-
await
|
|
7173
|
+
await fs28.rename(stage, destination);
|
|
6913
7174
|
} catch (error) {
|
|
6914
7175
|
try {
|
|
6915
7176
|
await verifyBundle(destination, bundle.manifestHash);
|
|
@@ -6918,7 +7179,7 @@ async function copyBundle(home, source, destination, bundle) {
|
|
|
6918
7179
|
}
|
|
6919
7180
|
}
|
|
6920
7181
|
} finally {
|
|
6921
|
-
await
|
|
7182
|
+
await fs28.rm(stage, { recursive: true, force: true });
|
|
6922
7183
|
}
|
|
6923
7184
|
}
|
|
6924
7185
|
async function prepareStandaloneMcp(home) {
|
|
@@ -7037,7 +7298,7 @@ async function uninstallStandalone() {
|
|
|
7037
7298
|
for (const [name, expected] of Object.entries(record.launchers)) {
|
|
7038
7299
|
if (!launcherNames.includes(name)) throw new Error("Invalid owned launcher name.");
|
|
7039
7300
|
const file = await storePath(record.bin, name);
|
|
7040
|
-
const actual = await
|
|
7301
|
+
const actual = await fs28.readFile(file, "utf8").catch((error) => {
|
|
7041
7302
|
if (error.code === "ENOENT") return null;
|
|
7042
7303
|
throw error;
|
|
7043
7304
|
});
|
|
@@ -7045,9 +7306,9 @@ async function uninstallStandalone() {
|
|
|
7045
7306
|
}
|
|
7046
7307
|
for (const warning of await removePath(record.pathChanges ?? [])) console.error(warning);
|
|
7047
7308
|
if (process.platform === "win32") await replace(await storePath(home, ".uninstall-request.txt", true), token);
|
|
7048
|
-
for (const name of Object.keys(record.launchers)) await
|
|
7309
|
+
for (const name of Object.keys(record.launchers)) await fs28.rm(await storePath(record.bin, name), { force: true });
|
|
7049
7310
|
});
|
|
7050
|
-
if (process.platform !== "win32") await
|
|
7311
|
+
if (process.platform !== "win32") await fs28.rm(home, { recursive: true, force: true });
|
|
7051
7312
|
return home;
|
|
7052
7313
|
}
|
|
7053
7314
|
var shQuote, psQuote, WINDOWS_BATCH_LAUNCHER, launcherNames;
|
|
@@ -7082,11 +7343,11 @@ __export(updates_exports, {
|
|
|
7082
7343
|
updateSettings: () => updateSettings,
|
|
7083
7344
|
updateStatus: () => updateStatus
|
|
7084
7345
|
});
|
|
7085
|
-
import
|
|
7346
|
+
import fs29 from "fs/promises";
|
|
7086
7347
|
import path34 from "path";
|
|
7087
7348
|
import { spawn as spawn2 } from "child_process";
|
|
7088
7349
|
import { randomUUID as randomUUID11 } from "crypto";
|
|
7089
|
-
import { z as
|
|
7350
|
+
import { z as z30 } from "zod";
|
|
7090
7351
|
async function boundedDownload(url) {
|
|
7091
7352
|
const response = await fetch(url, { signal: AbortSignal.timeout(15e3), redirect: "follow" });
|
|
7092
7353
|
if (!response.ok || !response.body) throw new Error(`Update metadata request failed (HTTP ${response.status}).`);
|
|
@@ -7189,7 +7450,7 @@ async function runAutomaticUpdate(home, dependencies = { fetchRelease: fetchVeri
|
|
|
7189
7450
|
} catch (error) {
|
|
7190
7451
|
status.error = error instanceof Error ? error.message : String(error);
|
|
7191
7452
|
}
|
|
7192
|
-
if (await
|
|
7453
|
+
if (await fs29.access(path34.join(home, "install.json")).then(() => true, () => false)) await writeStoreJson(home, "update-status.json", status);
|
|
7193
7454
|
}, 0);
|
|
7194
7455
|
}
|
|
7195
7456
|
async function scheduleAutomaticUpdate() {
|
|
@@ -7299,14 +7560,14 @@ var init_updates = __esm({
|
|
|
7299
7560
|
CHECK_INTERVAL_MS = 24 * 60 * 60 * 1e3;
|
|
7300
7561
|
RELEASE_AGE_MS = 24 * 60 * 60 * 1e3;
|
|
7301
7562
|
releaseBase = "https://github.com/adrianczuczka/mason/releases";
|
|
7302
|
-
releaseListSchema =
|
|
7303
|
-
updateManifestSchema =
|
|
7304
|
-
format:
|
|
7305
|
-
version:
|
|
7306
|
-
publishedAt:
|
|
7307
|
-
assets:
|
|
7563
|
+
releaseListSchema = z30.array(z30.object({ tag_name: z30.string(), draft: z30.boolean(), prerelease: z30.boolean(), published_at: z30.string().nullable() }));
|
|
7564
|
+
updateManifestSchema = z30.object({
|
|
7565
|
+
format: z30.literal(1),
|
|
7566
|
+
version: z30.string().regex(/^\d+\.\d+\.\d+$/),
|
|
7567
|
+
publishedAt: z30.string().datetime(),
|
|
7568
|
+
assets: z30.record(z30.string().regex(/^[a-f0-9]{64}$/))
|
|
7308
7569
|
}).strict();
|
|
7309
|
-
statusSchema2 =
|
|
7570
|
+
statusSchema2 = z30.object({ lastAttempt: z30.number(), availableVersion: z30.string().optional(), error: z30.string().optional() });
|
|
7310
7571
|
}
|
|
7311
7572
|
});
|
|
7312
7573
|
|
|
@@ -7314,6 +7575,7 @@ var init_updates = __esm({
|
|
|
7314
7575
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
7315
7576
|
|
|
7316
7577
|
// src/automation/cli.ts
|
|
7578
|
+
init_usefulness();
|
|
7317
7579
|
init_execution();
|
|
7318
7580
|
init_runtime();
|
|
7319
7581
|
init_adapters();
|
|
@@ -7385,13 +7647,27 @@ Keeps shared knowledge, repair evidence, ignore rules and the Mason installation
|
|
|
7385
7647
|
Edited or ambiguous entries are retained for manual cleanup. Restart assistants
|
|
7386
7648
|
after teardown. Run mason setup --host codex or --host claude to reconnect.
|
|
7387
7649
|
Exit 0: complete/no-op. Exit 2: cleanup needs attention (also in dry runs).`;
|
|
7388
|
-
var
|
|
7650
|
+
var STATS_USAGE = `Usage: mason stats [options]
|
|
7651
|
+
|
|
7652
|
+
Show aggregate local observations, or inspect one session.
|
|
7653
|
+
|
|
7654
|
+
--enable | --disable Enable/disable collection (setup enables it)
|
|
7655
|
+
--json Include session/finding IDs and evidence paths
|
|
7656
|
+
--dir <path> Project directory (defaults to cwd)
|
|
7657
|
+
--session <id> Filter to one retained session
|
|
7658
|
+
--session <id> --finding <id> --rating helpful|already-knew|irrelevant|deferred
|
|
7659
|
+
Supply feedback without changing audit status
|
|
7660
|
+
|
|
7661
|
+
No uploads. Observed resolution does not establish causation.`;
|
|
7662
|
+
var USAGE = `Usage: mason auto <setup|teardown|install|config|status|check|hook|stats> [options]
|
|
7389
7663
|
|
|
7390
7664
|
setup [--host claude|codex] Connect MCP, instructions and hooks to mason on PATH; retain the initial audit
|
|
7391
7665
|
teardown [--host claude|codex] Disconnect one host or all project hosts; retain knowledge and evidence
|
|
7392
7666
|
install --host claude|codex Merge lifecycle hooks into this project's host config
|
|
7393
7667
|
config --host claude|codex Print the host config without writing
|
|
7394
7668
|
status Read configured hooks and observed runtime events
|
|
7669
|
+
stats Aggregate local observations; --enable/--disable to configure
|
|
7670
|
+
Rate with --session <id> --finding <id> --rating helpful|already-knew|irrelevant|deferred
|
|
7395
7671
|
check Capture/resume and verify retained audit evidence
|
|
7396
7672
|
hook --host claude|codex Handle host JSON on stdin
|
|
7397
7673
|
|
|
@@ -7412,7 +7688,12 @@ var parseCli = (argv) => parseArgs({ args: argv, allowPositionals: true, options
|
|
|
7412
7688
|
json: { type: "boolean" },
|
|
7413
7689
|
help: { type: "boolean", short: "h" },
|
|
7414
7690
|
"dry-run": { type: "boolean" },
|
|
7415
|
-
profile: { type: "boolean" }
|
|
7691
|
+
profile: { type: "boolean" },
|
|
7692
|
+
enable: { type: "boolean" },
|
|
7693
|
+
disable: { type: "boolean" },
|
|
7694
|
+
session: { type: "string" },
|
|
7695
|
+
finding: { type: "string" },
|
|
7696
|
+
rating: { type: "string" }
|
|
7416
7697
|
} });
|
|
7417
7698
|
function isHookCommand(argv) {
|
|
7418
7699
|
try {
|
|
@@ -7432,12 +7713,14 @@ async function runAutomationCli(argv, stdin = "", io = defaultIo, options = {})
|
|
|
7432
7713
|
try {
|
|
7433
7714
|
const { values, positionals } = parseCli(argv);
|
|
7434
7715
|
if (values.help || !positionals.length) {
|
|
7435
|
-
io.out(positionals[0] === "teardown" ? TEARDOWN_USAGE : USAGE);
|
|
7716
|
+
io.out(positionals[0] === "teardown" ? TEARDOWN_USAGE : positionals[0] === "stats" ? STATS_USAGE : USAGE);
|
|
7436
7717
|
return 0;
|
|
7437
7718
|
}
|
|
7438
7719
|
if (positionals.length !== 1) throw new Error("Expected one command.");
|
|
7439
7720
|
[action] = positionals;
|
|
7440
7721
|
if (values.profile && !["check", "hook"].includes(action)) throw new Error("--profile applies only to check/hook.");
|
|
7722
|
+
const trackingOptions = [values.enable, values.disable, values.session, values.finding, values.rating].some((v) => v !== void 0);
|
|
7723
|
+
if (trackingOptions && action !== "stats") throw new Error("Tracking options apply only to stats.");
|
|
7441
7724
|
const measured = (run) => values.profile ? withProfile(run, io.err) : run();
|
|
7442
7725
|
const dir = values.dir ?? process.cwd();
|
|
7443
7726
|
if (values["dry-run"] !== void 0 && action !== "teardown") throw new Error("--dry-run applies only to teardown.");
|
|
@@ -7474,6 +7757,21 @@ async function runAutomationCli(argv, stdin = "", io = defaultIo, options = {})
|
|
|
7474
7757
|
return 0;
|
|
7475
7758
|
}
|
|
7476
7759
|
if (values.host || values.command) throw new Error("--host and --command apply only to install/config/hook.");
|
|
7760
|
+
if (action === "stats") {
|
|
7761
|
+
const configuring = values.enable !== void 0 || values.disable !== void 0;
|
|
7762
|
+
const rating = values.finding !== void 0 || values.rating !== void 0;
|
|
7763
|
+
if (values.enable !== void 0 && values.disable !== void 0 || configuring && (rating || values.session !== void 0)) throw new Error("Choose tracking configuration or session inspection/feedback separately.");
|
|
7764
|
+
if (rating && (!values.session || !values.finding || !values.rating)) throw new Error("Rating requires --session, --finding, and --rating.");
|
|
7765
|
+
if (configuring) {
|
|
7766
|
+
const result2 = await configureUsefulness(dir, !!values.enable);
|
|
7767
|
+
io.out(values.json ? JSON.stringify(result2, null, 2) : `Local stats ${result2.enabled ? "enabled" : "disabled"}. Existing observations retained. View with mason stats.`);
|
|
7768
|
+
return 0;
|
|
7769
|
+
}
|
|
7770
|
+
if (rating) await rateUsefulness(dir, values.session, values.finding, usefulnessRating.parse(values.rating));
|
|
7771
|
+
const result = await usefulnessStatus(dir, values.session);
|
|
7772
|
+
io.out(values.json ? JSON.stringify(result, null, 2) : summarizeUsefulness(result));
|
|
7773
|
+
return 0;
|
|
7774
|
+
}
|
|
7477
7775
|
if (action === "status") {
|
|
7478
7776
|
const { setupStatus: setupStatus2, summarizeActivation: summarizeActivation2 } = await Promise.resolve().then(() => (init_status(), status_exports));
|
|
7479
7777
|
const setup = await setupStatus2(dir);
|
|
@@ -7503,13 +7801,15 @@ var inputArgs = process.argv.slice(2);
|
|
|
7503
7801
|
var managedLaunch = inputArgs[0] === "--setup-host";
|
|
7504
7802
|
var setupHost = managedLaunch ? inputArgs.splice(0, 2)[1] : void 0;
|
|
7505
7803
|
var [command, ...args] = inputArgs;
|
|
7506
|
-
var usage = `Mason ${"0.
|
|
7804
|
+
var usage = `Mason ${"0.20.2"}
|
|
7507
7805
|
|
|
7508
7806
|
Usage: mason <command> [options]
|
|
7509
7807
|
|
|
7510
7808
|
setup --host codex|claude Connect this project to your assistant
|
|
7511
7809
|
teardown [--host codex|claude] Disconnect project integrations; retain knowledge
|
|
7512
7810
|
status Show configuration, observed use and verification
|
|
7811
|
+
stats Show local stats and feedback
|
|
7812
|
+
auto Configure and run lifecycle hooks
|
|
7513
7813
|
check Resume and verify retained documentation findings
|
|
7514
7814
|
audit Audit project instructions
|
|
7515
7815
|
review --base <ref> Review committed changes
|
|
@@ -7521,7 +7821,7 @@ Usage: mason <command> [options]
|
|
|
7521
7821
|
rollback Restore and pin the previous standalone version
|
|
7522
7822
|
uninstall Remove that installation; retain project data
|
|
7523
7823
|
|
|
7524
|
-
Use mason <command> --help for options. npm users can
|
|
7824
|
+
Use mason <command> --help for options. npm users can also use
|
|
7525
7825
|
mason-audit, mason-review, mason-drift, mason-hook and mason-mcp.`;
|
|
7526
7826
|
var progress;
|
|
7527
7827
|
try {
|
|
@@ -7532,9 +7832,9 @@ try {
|
|
|
7532
7832
|
await prepareLaunch2(setupHost, { allowInactive: command === "mcp" });
|
|
7533
7833
|
}
|
|
7534
7834
|
}
|
|
7535
|
-
if (command === "internal-integration-version") console.log(JSON.stringify({ protocol: 1, version: "0.
|
|
7835
|
+
if (command === "internal-integration-version") console.log(JSON.stringify({ protocol: 1, version: "0.20.2" }));
|
|
7536
7836
|
else if (!command || ["--help", "-h", "help"].includes(command)) console.log(usage);
|
|
7537
|
-
else if (["--version", "-v"].includes(command)) console.log("0.
|
|
7837
|
+
else if (["--version", "-v"].includes(command)) console.log("0.20.2");
|
|
7538
7838
|
else if (command === "internal-update") {
|
|
7539
7839
|
if (args.length) throw new Error("internal-update takes no arguments.");
|
|
7540
7840
|
const { currentInstallation: currentInstallation2 } = await Promise.resolve().then(() => (init_state(), state_exports));
|
|
@@ -7603,7 +7903,7 @@ Automatic updates: ${status.effective ? "enabled" : "inactive"} (preference: ${s
|
|
|
7603
7903
|
process.exitCode = await upgradeStandalone2(args[0], progress);
|
|
7604
7904
|
} 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.");
|
|
7605
7905
|
}
|
|
7606
|
-
} else if (["setup", "teardown", "status", "check", "auto"].includes(command)) {
|
|
7906
|
+
} else if (["setup", "teardown", "status", "check", "stats", "auto"].includes(command)) {
|
|
7607
7907
|
const forwarded = command === "auto" ? args : [command, ...args];
|
|
7608
7908
|
let input = "";
|
|
7609
7909
|
if (isHookCommand(forwarded) && !process.stdin.isTTY) for await (const chunk of process.stdin) {
|