etymd 0.10.0 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +66 -0
- package/README.md +23 -2
- package/dist/{approve-6TMXJLOI.js → approve-OUJ4UPGK.js} +5 -5
- package/dist/audit-A75FVXHD.js +12 -0
- package/dist/{brief-X64HK325.js → brief-MYUESUMM.js} +7 -7
- package/dist/{chunk-SGW7OPHZ.js → chunk-2KVQBL4E.js} +1 -1
- package/dist/{chunk-V2SOHPXF.js → chunk-2VLNI3L2.js} +1 -1
- package/dist/{chunk-OAFYLBVG.js → chunk-3E2IPCRY.js} +3 -3
- package/dist/{chunk-IV3FYVTS.js → chunk-4VPBP6K6.js} +4 -3
- package/dist/{chunk-WTD2EIMN.js → chunk-5GQ52DK6.js} +5 -5
- package/dist/{chunk-YVGC3E6U.js → chunk-DWL2IKZH.js} +3 -3
- package/dist/{chunk-IPOAD67U.js → chunk-F75Q43BC.js} +4 -4
- package/dist/{chunk-EF6BPBG6.js → chunk-GWGKEPRX.js} +2 -2
- package/dist/{chunk-R6CVJ5D7.js → chunk-HRJJQCMT.js} +9 -13
- package/dist/{chunk-K34AVLDP.js → chunk-J7XVOTZL.js} +1 -1
- package/dist/{chunk-C7LBUFNU.js → chunk-JHZ2BN4U.js} +3 -3
- package/dist/{chunk-KQMQJJZK.js → chunk-JVLWM27I.js} +9 -9
- package/dist/{chunk-OIYNP6Y6.js → chunk-LSZGCKIQ.js} +1 -1
- package/dist/cli.js +19 -19
- package/dist/config-XAH6PA5G.js +4 -0
- package/dist/{context-K6DZNWAV.js → context-F63RSIBH.js} +4 -4
- package/dist/doctor-OZQTOUFR.js +19 -0
- package/dist/{fleet-4PPNUIDJ.js → fleet-Z7W3QBEV.js} +18 -18
- package/dist/{gates-Q7FMVUQP.js → gates-47YTMMLZ.js} +10 -10
- package/dist/generate-PMCP37DC.js +6 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +137 -141
- package/dist/{init-YYGRFIHQ.js → init-QLGIYFCX.js} +10 -10
- package/dist/ledger-4FOZ5HAB.js +5 -0
- package/dist/{scan-VO776GM7.js → scan-VWEXWM6G.js} +5 -5
- package/dist/scan-W7RHZVSF.js +5 -0
- package/dist/{screen-MOQP6XC7.js → screen-NSWNC7VQ.js} +80 -10
- package/package.json +4 -4
- package/dist/audit-FGDX2AYS.js +0 -12
- package/dist/config-TPK5LYDM.js +0 -4
- package/dist/doctor-W3UZK7KS.js +0 -19
- package/dist/generate-NLHGTRZB.js +0 -6
- package/dist/ledger-B2XGANTH.js +0 -5
- package/dist/scan-PJSZGUG7.js +0 -5
package/dist/index.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { execFile } from '
|
|
2
|
-
import { promises } from '
|
|
3
|
-
import
|
|
4
|
-
import
|
|
5
|
-
import { createHash } from '
|
|
1
|
+
import { execFile } from 'child_process';
|
|
2
|
+
import { promises } from 'fs';
|
|
3
|
+
import path2 from 'path';
|
|
4
|
+
import { promisify } from 'util';
|
|
5
|
+
import { createHash } from 'crypto';
|
|
6
6
|
import YAML from 'yaml';
|
|
7
|
-
import os from '
|
|
7
|
+
import os from 'os';
|
|
8
8
|
|
|
9
9
|
var __defProp = Object.defineProperty;
|
|
10
10
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
@@ -20,7 +20,7 @@ var __export = (target, all) => {
|
|
|
20
20
|
var PACK_VERSION;
|
|
21
21
|
var init_version = __esm({
|
|
22
22
|
"src/pack/version.ts"() {
|
|
23
|
-
PACK_VERSION = "
|
|
23
|
+
PACK_VERSION = "8";
|
|
24
24
|
}
|
|
25
25
|
});
|
|
26
26
|
|
|
@@ -30,7 +30,7 @@ var init_package = __esm({
|
|
|
30
30
|
"package.json"() {
|
|
31
31
|
package_default = {
|
|
32
32
|
name: "etymd",
|
|
33
|
-
version: "0.
|
|
33
|
+
version: "0.11.0",
|
|
34
34
|
description: "Keep your agent instructions true \u2014 verify AGENTS.md, CLAUDE.md, rules & skills against the actual repo, with drift caught over time and a regression ledger.",
|
|
35
35
|
keywords: [
|
|
36
36
|
"cli",
|
|
@@ -99,12 +99,12 @@ var init_package = __esm({
|
|
|
99
99
|
yaml: "2.9.0"
|
|
100
100
|
},
|
|
101
101
|
devDependencies: {
|
|
102
|
-
"@changesets/cli": "2.
|
|
102
|
+
"@changesets/cli": "2.31.1",
|
|
103
103
|
"@types/node": "22.10.2",
|
|
104
104
|
prettier: "3.4.2",
|
|
105
|
-
tsup: "8.
|
|
105
|
+
tsup: "8.5.1",
|
|
106
106
|
typescript: "5.7.2",
|
|
107
|
-
vitest: "
|
|
107
|
+
vitest: "4.1.10"
|
|
108
108
|
},
|
|
109
109
|
publishConfig: {
|
|
110
110
|
access: "public"
|
|
@@ -244,15 +244,15 @@ async function detectPackageManager(root, pkg) {
|
|
|
244
244
|
["bun.lockb", "bun"]
|
|
245
245
|
];
|
|
246
246
|
for (const [file, pm] of lock) {
|
|
247
|
-
if (await pathExists(
|
|
247
|
+
if (await pathExists(path2.join(root, file))) return pm;
|
|
248
248
|
}
|
|
249
249
|
return "unknown";
|
|
250
250
|
}
|
|
251
251
|
async function detectWorkspace(root, pkg) {
|
|
252
|
-
if (await pathExists(
|
|
253
|
-
if (await pathExists(
|
|
254
|
-
if (await pathExists(
|
|
255
|
-
const pnpmWs = await readText(
|
|
252
|
+
if (await pathExists(path2.join(root, "nx.json"))) return { kind: "nx", packageGlobs: [] };
|
|
253
|
+
if (await pathExists(path2.join(root, "turbo.json"))) return { kind: "turbo", packageGlobs: [] };
|
|
254
|
+
if (await pathExists(path2.join(root, "lerna.json"))) return { kind: "lerna", packageGlobs: [] };
|
|
255
|
+
const pnpmWs = await readText(path2.join(root, "pnpm-workspace.yaml"));
|
|
256
256
|
if (pnpmWs) {
|
|
257
257
|
const globs = pnpmWs.split("\n").map((l) => l.match(/^\s*-\s*["']?([^"'#]+)["']?/)?.[1]?.trim()).filter((g) => Boolean(g));
|
|
258
258
|
return { kind: "pnpm", packageGlobs: globs };
|
|
@@ -270,21 +270,21 @@ async function listWorkspacePackages(root, globs) {
|
|
|
270
270
|
const found = [];
|
|
271
271
|
const seen = /* @__PURE__ */ new Set();
|
|
272
272
|
const addFromDir = async (dir) => {
|
|
273
|
-
const pkgPath =
|
|
273
|
+
const pkgPath = path2.join(dir, "package.json");
|
|
274
274
|
if (seen.has(pkgPath)) return;
|
|
275
275
|
const pkg = await readJson(pkgPath);
|
|
276
276
|
if (!pkg?.name) return;
|
|
277
277
|
seen.add(pkgPath);
|
|
278
278
|
found.push({
|
|
279
279
|
name: pkg.name,
|
|
280
|
-
dir:
|
|
280
|
+
dir: path2.relative(root, dir) || ".",
|
|
281
281
|
private: Boolean(pkg.private),
|
|
282
282
|
version: pkg.version
|
|
283
283
|
});
|
|
284
284
|
};
|
|
285
285
|
for (const glob of globs) {
|
|
286
286
|
const base = glob.replace(/\/\*\*?$/, "");
|
|
287
|
-
const parent =
|
|
287
|
+
const parent = path2.join(root, base);
|
|
288
288
|
if (!await isDirectory(parent)) continue;
|
|
289
289
|
let entries;
|
|
290
290
|
try {
|
|
@@ -294,7 +294,7 @@ async function listWorkspacePackages(root, globs) {
|
|
|
294
294
|
}
|
|
295
295
|
for (const entry of entries) {
|
|
296
296
|
if (IGNORED_DIRS.has(entry) || entry.startsWith(".")) continue;
|
|
297
|
-
const child =
|
|
297
|
+
const child = path2.join(parent, entry);
|
|
298
298
|
if (await isDirectory(child)) await addFromDir(child);
|
|
299
299
|
}
|
|
300
300
|
}
|
|
@@ -307,7 +307,7 @@ async function expandFileGlobs(root, globs, cap = 500) {
|
|
|
307
307
|
if (out.size >= cap) return;
|
|
308
308
|
let entries;
|
|
309
309
|
try {
|
|
310
|
-
entries = await promises.readdir(
|
|
310
|
+
entries = await promises.readdir(path2.join(root, rel), { withFileTypes: true });
|
|
311
311
|
} catch {
|
|
312
312
|
return;
|
|
313
313
|
}
|
|
@@ -334,7 +334,7 @@ async function expandFileGlobs(root, globs, cap = 500) {
|
|
|
334
334
|
(r) => ![...roots].some((other) => other !== r && (other === "" || r.startsWith(other + "/")))
|
|
335
335
|
);
|
|
336
336
|
for (const r of minimal) {
|
|
337
|
-
if (r && !await isDirectory(
|
|
337
|
+
if (r && !await isDirectory(path2.join(root, r))) continue;
|
|
338
338
|
await walk(r);
|
|
339
339
|
}
|
|
340
340
|
return [...out].sort();
|
|
@@ -363,13 +363,13 @@ function detectFrameworks(pkg) {
|
|
|
363
363
|
return out;
|
|
364
364
|
}
|
|
365
365
|
async function detectCi(root) {
|
|
366
|
-
if (await pathExists(
|
|
366
|
+
if (await pathExists(path2.join(root, ".gitlab-ci.yml"))) {
|
|
367
367
|
return { system: "gitlab", files: [".gitlab-ci.yml"] };
|
|
368
368
|
}
|
|
369
|
-
const workflows =
|
|
369
|
+
const workflows = path2.join(root, ".github", "workflows");
|
|
370
370
|
if (await isDirectory(workflows)) {
|
|
371
371
|
try {
|
|
372
|
-
const files = (await promises.readdir(workflows)).filter((f) => f.endsWith(".yml") || f.endsWith(".yaml")).map((f) =>
|
|
372
|
+
const files = (await promises.readdir(workflows)).filter((f) => f.endsWith(".yml") || f.endsWith(".yaml")).map((f) => path2.join(".github/workflows", f));
|
|
373
373
|
if (files.length) return { system: "github", files };
|
|
374
374
|
} catch {
|
|
375
375
|
}
|
|
@@ -384,7 +384,7 @@ async function hasLintStagedConfig(root, pkg) {
|
|
|
384
384
|
".lintstagedrc.js",
|
|
385
385
|
"lint-staged.config.js"
|
|
386
386
|
]) {
|
|
387
|
-
if (await pathExists(
|
|
387
|
+
if (await pathExists(path2.join(root, f))) return true;
|
|
388
388
|
}
|
|
389
389
|
return false;
|
|
390
390
|
}
|
|
@@ -392,50 +392,50 @@ async function detectHooks(root, hooksPath, pkg) {
|
|
|
392
392
|
const lintStaged = await hasLintStagedConfig(root, pkg);
|
|
393
393
|
if (hooksPath) {
|
|
394
394
|
if (hooksPath.replace(/\/+$/, "") === ".husky/_") {
|
|
395
|
-
const base2 =
|
|
395
|
+
const base2 = path2.join(root, ".husky");
|
|
396
396
|
return {
|
|
397
397
|
source: "husky",
|
|
398
398
|
dir: ".husky",
|
|
399
|
-
preCommit: await pathExists(
|
|
400
|
-
prePush: await pathExists(
|
|
401
|
-
commitMsg: await pathExists(
|
|
399
|
+
preCommit: await pathExists(path2.join(base2, "pre-commit")),
|
|
400
|
+
prePush: await pathExists(path2.join(base2, "pre-push")),
|
|
401
|
+
commitMsg: await pathExists(path2.join(base2, "commit-msg")),
|
|
402
402
|
lintStaged
|
|
403
403
|
};
|
|
404
404
|
}
|
|
405
|
-
const base =
|
|
405
|
+
const base = path2.join(root, hooksPath);
|
|
406
406
|
return {
|
|
407
407
|
source: hooksPath === ".githooks" ? "githooks" : "custom",
|
|
408
408
|
dir: hooksPath,
|
|
409
|
-
preCommit: await pathExists(
|
|
410
|
-
prePush: await pathExists(
|
|
411
|
-
commitMsg: await pathExists(
|
|
409
|
+
preCommit: await pathExists(path2.join(base, "pre-commit")),
|
|
410
|
+
prePush: await pathExists(path2.join(base, "pre-push")),
|
|
411
|
+
commitMsg: await pathExists(path2.join(base, "commit-msg")),
|
|
412
412
|
lintStaged
|
|
413
413
|
};
|
|
414
414
|
}
|
|
415
|
-
if (await isDirectory(
|
|
416
|
-
const base =
|
|
415
|
+
if (await isDirectory(path2.join(root, ".githooks"))) {
|
|
416
|
+
const base = path2.join(root, ".githooks");
|
|
417
417
|
return {
|
|
418
418
|
source: "githooks",
|
|
419
419
|
dir: ".githooks",
|
|
420
|
-
preCommit: await pathExists(
|
|
421
|
-
prePush: await pathExists(
|
|
422
|
-
commitMsg: await pathExists(
|
|
420
|
+
preCommit: await pathExists(path2.join(base, "pre-commit")),
|
|
421
|
+
prePush: await pathExists(path2.join(base, "pre-push")),
|
|
422
|
+
commitMsg: await pathExists(path2.join(base, "commit-msg")),
|
|
423
423
|
lintStaged
|
|
424
424
|
};
|
|
425
425
|
}
|
|
426
|
-
if (await isDirectory(
|
|
427
|
-
const base =
|
|
426
|
+
if (await isDirectory(path2.join(root, ".husky"))) {
|
|
427
|
+
const base = path2.join(root, ".husky");
|
|
428
428
|
return {
|
|
429
429
|
source: "husky",
|
|
430
430
|
dir: ".husky",
|
|
431
|
-
preCommit: await pathExists(
|
|
432
|
-
prePush: await pathExists(
|
|
433
|
-
commitMsg: await pathExists(
|
|
431
|
+
preCommit: await pathExists(path2.join(base, "pre-commit")),
|
|
432
|
+
prePush: await pathExists(path2.join(base, "pre-push")),
|
|
433
|
+
commitMsg: await pathExists(path2.join(base, "commit-msg")),
|
|
434
434
|
lintStaged
|
|
435
435
|
};
|
|
436
436
|
}
|
|
437
437
|
const legacyHooks = pkg?.husky?.hooks;
|
|
438
|
-
const configText = legacyHooks ? null : await readText(
|
|
438
|
+
const configText = legacyHooks ? null : await readText(path2.join(root, "husky.config.js"));
|
|
439
439
|
if (legacyHooks || configText) {
|
|
440
440
|
const hookNamed = (name) => legacyHooks ? Boolean(legacyHooks[name]) : Boolean(configText && configText.includes(name));
|
|
441
441
|
return {
|
|
@@ -466,10 +466,10 @@ async function detectShellSurface(root, isRepo) {
|
|
|
466
466
|
if (!listed) return { scripts: 0 };
|
|
467
467
|
const files = listed.split("\0").filter(Boolean);
|
|
468
468
|
const byExtension = files.filter((f) => SHELL_EXT.test(f));
|
|
469
|
-
const candidates = files.filter((f) => !
|
|
469
|
+
const candidates = files.filter((f) => !path2.basename(f).includes("."));
|
|
470
470
|
const SHEBANG_PROBE_CAP = 400;
|
|
471
471
|
const probed = await Promise.all(
|
|
472
|
-
candidates.slice(0, SHEBANG_PROBE_CAP).map(async (f) => SHELL_SHEBANG.test(await firstLine(
|
|
472
|
+
candidates.slice(0, SHEBANG_PROBE_CAP).map(async (f) => SHELL_SHEBANG.test(await firstLine(path2.join(root, f))))
|
|
473
473
|
);
|
|
474
474
|
return { scripts: byExtension.length + probed.filter(Boolean).length };
|
|
475
475
|
}
|
|
@@ -477,12 +477,12 @@ async function detectArtifacts(root) {
|
|
|
477
477
|
const artifacts = await Promise.all(
|
|
478
478
|
ARTIFACT_SPECS.map(async (spec) => ({
|
|
479
479
|
...spec,
|
|
480
|
-
exists: await pathExists(
|
|
480
|
+
exists: await pathExists(path2.join(root, spec.path))
|
|
481
481
|
}))
|
|
482
482
|
);
|
|
483
483
|
let adrFiles = false;
|
|
484
484
|
try {
|
|
485
|
-
adrFiles = (await promises.readdir(
|
|
485
|
+
adrFiles = (await promises.readdir(path2.join(root, "docs"))).some((f) => /^\d{4}-.+\.md$/.test(f));
|
|
486
486
|
} catch {
|
|
487
487
|
}
|
|
488
488
|
artifacts.push({
|
|
@@ -511,7 +511,7 @@ async function walkTree(root) {
|
|
|
511
511
|
for (const entry of entries) {
|
|
512
512
|
if (entry.name.startsWith(".") || IGNORED_DIRS.has(entry.name)) continue;
|
|
513
513
|
if (entry.isDirectory()) {
|
|
514
|
-
total += await countFiles(
|
|
514
|
+
total += await countFiles(path2.join(dir, entry.name));
|
|
515
515
|
} else if (entry.isFile()) {
|
|
516
516
|
total += 1;
|
|
517
517
|
state.budget -= 1;
|
|
@@ -534,7 +534,7 @@ async function walkTree(root) {
|
|
|
534
534
|
if (!entry.isDirectory()) continue;
|
|
535
535
|
if (IGNORED_DIRS.has(entry.name)) continue;
|
|
536
536
|
if (entry.name.startsWith(".") && !MEANINGFUL_DOT_DIRS.has(entry.name)) continue;
|
|
537
|
-
dirs.push({ name: entry.name, files: await countFiles(
|
|
537
|
+
dirs.push({ name: entry.name, files: await countFiles(path2.join(root, entry.name)) });
|
|
538
538
|
}
|
|
539
539
|
dirs.sort((a, b) => b.files - a.files);
|
|
540
540
|
return { dirs, truncated: state.truncated };
|
|
@@ -782,8 +782,8 @@ async function collectFreshness(abs, isRepo, artifacts, upstreamRemote) {
|
|
|
782
782
|
return facts;
|
|
783
783
|
}
|
|
784
784
|
async function scanProject(root, opts = {}) {
|
|
785
|
-
const abs =
|
|
786
|
-
const rootPkg = await readJson(
|
|
785
|
+
const abs = path2.resolve(root);
|
|
786
|
+
const rootPkg = await readJson(path2.join(abs, "package.json"));
|
|
787
787
|
const [packageManager, workspace, ci, artifacts, tree, isRepo] = await Promise.all([
|
|
788
788
|
detectPackageManager(abs, rootPkg),
|
|
789
789
|
detectWorkspace(abs, rootPkg),
|
|
@@ -810,7 +810,7 @@ async function scanProject(root, opts = {}) {
|
|
|
810
810
|
packVersion: PACK_VERSION,
|
|
811
811
|
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
812
812
|
root: abs,
|
|
813
|
-
name: rootPkg?.name ??
|
|
813
|
+
name: rootPkg?.name ?? path2.basename(abs),
|
|
814
814
|
git: {
|
|
815
815
|
isRepo,
|
|
816
816
|
branch: branch ?? void 0,
|
|
@@ -847,13 +847,13 @@ var init_scan = __esm({
|
|
|
847
847
|
}
|
|
848
848
|
});
|
|
849
849
|
function cacheFactsPath(root) {
|
|
850
|
-
return
|
|
850
|
+
return path2.join(root, CACHE_FACTS_FILE);
|
|
851
851
|
}
|
|
852
852
|
function baselinePath(root) {
|
|
853
|
-
return
|
|
853
|
+
return path2.join(root, BASELINE_FILE);
|
|
854
854
|
}
|
|
855
855
|
async function writeJsonFile(target, data) {
|
|
856
|
-
await promises.mkdir(
|
|
856
|
+
await promises.mkdir(path2.dirname(target), { recursive: true });
|
|
857
857
|
await promises.writeFile(target, JSON.stringify(data, null, 2) + "\n", "utf8");
|
|
858
858
|
return target;
|
|
859
859
|
}
|
|
@@ -887,9 +887,9 @@ var init_facts = __esm({
|
|
|
887
887
|
"src/core/facts.ts"() {
|
|
888
888
|
init_util();
|
|
889
889
|
ETYMD_DIR = ".etymd";
|
|
890
|
-
CACHE_DIR =
|
|
891
|
-
CACHE_FACTS_FILE =
|
|
892
|
-
BASELINE_FILE =
|
|
890
|
+
CACHE_DIR = path2.join(ETYMD_DIR, "cache");
|
|
891
|
+
CACHE_FACTS_FILE = path2.join(CACHE_DIR, "facts.json");
|
|
892
|
+
BASELINE_FILE = path2.join(ETYMD_DIR, "baseline.json");
|
|
893
893
|
}
|
|
894
894
|
});
|
|
895
895
|
|
|
@@ -902,7 +902,7 @@ __export(config_exports, {
|
|
|
902
902
|
readConfig: () => readConfig
|
|
903
903
|
});
|
|
904
904
|
function configPath(root) {
|
|
905
|
-
return
|
|
905
|
+
return path2.join(root, CONFIG_FILE);
|
|
906
906
|
}
|
|
907
907
|
function readGlobList(value, key, problems) {
|
|
908
908
|
if (value === void 0) return void 0;
|
|
@@ -1001,7 +1001,7 @@ var init_config = __esm({
|
|
|
1001
1001
|
"src/core/config.ts"() {
|
|
1002
1002
|
init_facts();
|
|
1003
1003
|
init_util();
|
|
1004
|
-
CONFIG_FILE =
|
|
1004
|
+
CONFIG_FILE = path2.join(ETYMD_DIR, "config.json");
|
|
1005
1005
|
DEFAULT_CONFIG = {
|
|
1006
1006
|
instructions: { include: [], exclude: [] },
|
|
1007
1007
|
context: { perFileWords: 4e3, totalWords: 8e3 },
|
|
@@ -1269,7 +1269,7 @@ ${auditStep}
|
|
|
1269
1269
|
# with --no-verify and anything a rebase or merge brought in from elsewhere. Advisory here (it
|
|
1270
1270
|
# never blocks the push): the blocking decision belongs at commit time, where the fix is cheap.
|
|
1271
1271
|
${CONTENT_GATE_RESOLUTION}
|
|
1272
|
-
if [ -x "$GATE" ]
|
|
1272
|
+
if [ -x "$GATE" ]; then
|
|
1273
1273
|
"$GATE" screen --tree --advisory || true
|
|
1274
1274
|
fi
|
|
1275
1275
|
|
|
@@ -1283,14 +1283,10 @@ function generateArtifactCheckScript() {
|
|
|
1283
1283
|
# Wire it into the irreversible moment:
|
|
1284
1284
|
# package.json \u2192 "prepublishOnly": "./scripts/artifact-check.sh"
|
|
1285
1285
|
#
|
|
1286
|
-
#
|
|
1286
|
+
# The artifact gate is the one check that sees what actually SHIPS \u2014 bypass with
|
|
1287
|
+
# .etymd-screen-allow entries (with provenance) if you must exempt a string.
|
|
1287
1288
|
set -eu
|
|
1288
1289
|
|
|
1289
|
-
if [ "\${ARTIFACT_CHECK_SKIP:-0}" = "1" ]; then
|
|
1290
|
-
echo "\u203A artifact-check: SKIPPED by ARTIFACT_CHECK_SKIP=1"
|
|
1291
|
-
exit 0
|
|
1292
|
-
fi
|
|
1293
|
-
|
|
1294
1290
|
${CONTENT_GATE_RESOLUTION}
|
|
1295
1291
|
[ -x "$GATE" ] || { echo "\u203A artifact-check: no checker installed \u2014 skipping."; exit 0; }
|
|
1296
1292
|
|
|
@@ -1313,7 +1309,7 @@ var init_templates = __esm({
|
|
|
1313
1309
|
"src/pack/templates.ts"() {
|
|
1314
1310
|
init_version();
|
|
1315
1311
|
GENERATION_MARKER_RE = /^(?:# |<!-- )etymd:generated pack-v\S+ ([0-9a-f]{16})(?: -->)?$/;
|
|
1316
|
-
CONTENT_GATE_RESOLUTION = `GATE="\${CONTENT_GATE:-$(command -v etymd || true)}"`;
|
|
1312
|
+
CONTENT_GATE_RESOLUTION = `GATE="\${CONTENT_GATE:-$(if [ -x ./dist/cli.js ]; then echo ./dist/cli.js; else command -v etymd || true; fi)}"`;
|
|
1317
1313
|
COMMIT_TYPES = [
|
|
1318
1314
|
"feat",
|
|
1319
1315
|
"fix",
|
|
@@ -1370,7 +1366,7 @@ function deriveFailOn(facts, recorded) {
|
|
|
1370
1366
|
async function planWorkflow(root, facts, opts) {
|
|
1371
1367
|
const out = [];
|
|
1372
1368
|
const add = async (rel, contents, label, executable = false) => {
|
|
1373
|
-
const abs =
|
|
1369
|
+
const abs = path2.join(root, rel);
|
|
1374
1370
|
const exists = await pathExists(abs);
|
|
1375
1371
|
const existing = exists ? await readText(abs) : null;
|
|
1376
1372
|
const differs = exists ? existing !== contents : void 0;
|
|
@@ -1389,7 +1385,7 @@ async function planWorkflow(root, facts, opts) {
|
|
|
1389
1385
|
await add("AGENTS.md", generateAgentsMd(facts), "Minimal operating contract (scaffold)");
|
|
1390
1386
|
}
|
|
1391
1387
|
if (opts.gates) {
|
|
1392
|
-
const existingPrePush = await readText(
|
|
1388
|
+
const existingPrePush = await readText(path2.join(root, ".githooks", "pre-push"));
|
|
1393
1389
|
await add(".githooks/pre-commit", generatePreCommitHook(), "Process gate (pre-commit)", true);
|
|
1394
1390
|
await add(
|
|
1395
1391
|
".githooks/commit-msg",
|
|
@@ -1453,18 +1449,18 @@ function isAlwaysAppliedCursorRule(text) {
|
|
|
1453
1449
|
async function measureContext(root, perFileWords = EXTRACTION_THRESHOLD) {
|
|
1454
1450
|
const files = [];
|
|
1455
1451
|
for (const spec of ALWAYS_LOADED) {
|
|
1456
|
-
const abs =
|
|
1452
|
+
const abs = path2.join(root, spec.path);
|
|
1457
1453
|
const text = await readText(abs);
|
|
1458
1454
|
if (text === null) continue;
|
|
1459
1455
|
const words = wordCount(text);
|
|
1460
1456
|
files.push({ path: spec.path, role: spec.role, words, approxTokens: approxTokens(words) });
|
|
1461
1457
|
}
|
|
1462
|
-
const rulesDir =
|
|
1458
|
+
const rulesDir = path2.join(root, ".cursor", "rules");
|
|
1463
1459
|
if (await pathExists(rulesDir)) {
|
|
1464
1460
|
try {
|
|
1465
1461
|
for (const entry of await promises.readdir(rulesDir)) {
|
|
1466
1462
|
if (!entry.endsWith(".mdc") && !entry.endsWith(".md")) continue;
|
|
1467
|
-
const text = await readText(
|
|
1463
|
+
const text = await readText(path2.join(rulesDir, entry));
|
|
1468
1464
|
if (text === null || !isAlwaysAppliedCursorRule(text)) continue;
|
|
1469
1465
|
const words = wordCount(text);
|
|
1470
1466
|
files.push({
|
|
@@ -1497,8 +1493,8 @@ async function applyFiles(root, files, overwrite = /* @__PURE__ */ new Set()) {
|
|
|
1497
1493
|
skipped.push(file.path);
|
|
1498
1494
|
continue;
|
|
1499
1495
|
}
|
|
1500
|
-
const abs =
|
|
1501
|
-
await promises.mkdir(
|
|
1496
|
+
const abs = path2.join(root, file.path);
|
|
1497
|
+
await promises.mkdir(path2.dirname(abs), { recursive: true });
|
|
1502
1498
|
await promises.writeFile(abs, file.contents, "utf8");
|
|
1503
1499
|
if (file.executable) await promises.chmod(abs, 493);
|
|
1504
1500
|
written.push(file.path);
|
|
@@ -1799,14 +1795,14 @@ function parseGithubWorkflow(text, file, scripts) {
|
|
|
1799
1795
|
return { jobs, parseErrors };
|
|
1800
1796
|
}
|
|
1801
1797
|
async function companionOf(root, hooksDir, hook, hookText, scripts) {
|
|
1802
|
-
const rel =
|
|
1798
|
+
const rel = path2.posix.join(hooksDir.split(path2.sep).join("/"), `${hook}.local`);
|
|
1803
1799
|
if (!hookText.includes(`${hook}.local`)) return null;
|
|
1804
|
-
const abs =
|
|
1800
|
+
const abs = path2.join(root, hooksDir, `${hook}.local`);
|
|
1805
1801
|
const text = await readText(abs);
|
|
1806
1802
|
if (text === null) return null;
|
|
1807
1803
|
const [executable, hookExecutable] = await Promise.all([
|
|
1808
1804
|
isExecutable(abs),
|
|
1809
|
-
isExecutable(
|
|
1805
|
+
isExecutable(path2.join(root, hooksDir, hook))
|
|
1810
1806
|
]);
|
|
1811
1807
|
if (executable === false && hookExecutable === true) return { rel, tools: [], state: "inert" };
|
|
1812
1808
|
return {
|
|
@@ -1823,7 +1819,7 @@ async function localHookTools(root, facts, scripts) {
|
|
|
1823
1819
|
const inertCompanions = [];
|
|
1824
1820
|
const readHook = async (name) => {
|
|
1825
1821
|
if (!hooks.dir) return empty;
|
|
1826
|
-
const text = await readText(
|
|
1822
|
+
const text = await readText(path2.join(root, hooks.dir, name));
|
|
1827
1823
|
if (!text) return empty;
|
|
1828
1824
|
const tools = new Set(matchTools(text, scripts));
|
|
1829
1825
|
const companion = await companionOf(root, hooks.dir, name, text, scripts);
|
|
@@ -1847,10 +1843,10 @@ async function localHookTools(root, facts, scripts) {
|
|
|
1847
1843
|
]);
|
|
1848
1844
|
} else if (hooks.source === "husky-legacy") {
|
|
1849
1845
|
const pkg = await readJson(
|
|
1850
|
-
|
|
1846
|
+
path2.join(root, "package.json")
|
|
1851
1847
|
);
|
|
1852
1848
|
const legacy = pkg?.husky?.hooks ?? {};
|
|
1853
|
-
const configText = await readText(
|
|
1849
|
+
const configText = await readText(path2.join(root, "husky.config.js"));
|
|
1854
1850
|
const forHook = (name) => {
|
|
1855
1851
|
const cmd = legacy[name] ?? (configText?.includes(name) ? configText : void 0);
|
|
1856
1852
|
return cmd ? matchTools(cmd, scripts) : [];
|
|
@@ -1861,7 +1857,7 @@ async function localHookTools(root, facts, scripts) {
|
|
|
1861
1857
|
}
|
|
1862
1858
|
let lintStaged = [];
|
|
1863
1859
|
if (hooks.lintStaged) {
|
|
1864
|
-
const pkg = await readJson(
|
|
1860
|
+
const pkg = await readJson(path2.join(root, "package.json"));
|
|
1865
1861
|
const config = pkg?.["lint-staged"];
|
|
1866
1862
|
const commands = [];
|
|
1867
1863
|
const collect = (v) => {
|
|
@@ -1891,7 +1887,7 @@ async function localHookTools(root, facts, scripts) {
|
|
|
1891
1887
|
};
|
|
1892
1888
|
}
|
|
1893
1889
|
async function detectThresholds(root, scripts, ciJobs) {
|
|
1894
|
-
const sonarConfigured = await pathExists(
|
|
1890
|
+
const sonarConfigured = await pathExists(path2.join(root, "sonar-project.properties")) || ciJobs.some((j) => j.tools.includes("sonar"));
|
|
1895
1891
|
let coverageThresholdLocal = false;
|
|
1896
1892
|
for (const f of [
|
|
1897
1893
|
"jest.config.js",
|
|
@@ -1903,7 +1899,7 @@ async function detectThresholds(root, scripts, ciJobs) {
|
|
|
1903
1899
|
"vitest.config.js",
|
|
1904
1900
|
"vitest.config.mts"
|
|
1905
1901
|
]) {
|
|
1906
|
-
const text = await readText(
|
|
1902
|
+
const text = await readText(path2.join(root, f));
|
|
1907
1903
|
if (text && /coverageThreshold|thresholds\s*:/.test(text)) {
|
|
1908
1904
|
coverageThresholdLocal = true;
|
|
1909
1905
|
break;
|
|
@@ -1911,7 +1907,7 @@ async function detectThresholds(root, scripts, ciJobs) {
|
|
|
1911
1907
|
}
|
|
1912
1908
|
if (!coverageThresholdLocal) {
|
|
1913
1909
|
const pkg = await readJson(
|
|
1914
|
-
|
|
1910
|
+
path2.join(root, "package.json")
|
|
1915
1911
|
);
|
|
1916
1912
|
coverageThresholdLocal = Boolean(pkg?.jest?.coverageThreshold);
|
|
1917
1913
|
}
|
|
@@ -1924,7 +1920,7 @@ async function buildGateInventory(root, facts) {
|
|
|
1924
1920
|
const inheritedIncludes = [];
|
|
1925
1921
|
const parseErrors = [];
|
|
1926
1922
|
if (facts.ci.system === "gitlab") {
|
|
1927
|
-
const text = await readText(
|
|
1923
|
+
const text = await readText(path2.join(root, ".gitlab-ci.yml"));
|
|
1928
1924
|
if (text) {
|
|
1929
1925
|
const parsed = parseGitlabCi(text, ".gitlab-ci.yml", scripts);
|
|
1930
1926
|
jobs.push(...parsed.jobs);
|
|
@@ -1932,7 +1928,7 @@ async function buildGateInventory(root, facts) {
|
|
|
1932
1928
|
for (const inc of parsed.inheritedIncludes) {
|
|
1933
1929
|
if (inc.startsWith("local: ")) {
|
|
1934
1930
|
const rel = inc.slice("local: ".length).replace(/^\//, "");
|
|
1935
|
-
const subText = await readText(
|
|
1931
|
+
const subText = await readText(path2.join(root, rel));
|
|
1936
1932
|
if (subText) {
|
|
1937
1933
|
const sub = parseGitlabCi(subText, rel, scripts);
|
|
1938
1934
|
jobs.push(...sub.jobs);
|
|
@@ -1948,7 +1944,7 @@ async function buildGateInventory(root, facts) {
|
|
|
1948
1944
|
}
|
|
1949
1945
|
} else if (facts.ci.system === "github") {
|
|
1950
1946
|
for (const file of facts.ci.files) {
|
|
1951
|
-
const text = await readText(
|
|
1947
|
+
const text = await readText(path2.join(root, file));
|
|
1952
1948
|
if (!text) continue;
|
|
1953
1949
|
const parsed = parseGithubWorkflow(text, file, scripts);
|
|
1954
1950
|
jobs.push(...parsed.jobs);
|
|
@@ -1959,7 +1955,7 @@ async function buildGateInventory(root, facts) {
|
|
|
1959
1955
|
localHookTools(root, facts, scripts),
|
|
1960
1956
|
detectThresholds(root, scripts, jobs)
|
|
1961
1957
|
]);
|
|
1962
|
-
const pkg = await readJson(
|
|
1958
|
+
const pkg = await readJson(path2.join(root, "package.json"));
|
|
1963
1959
|
const deps = { ...pkg?.dependencies, ...pkg?.devDependencies };
|
|
1964
1960
|
const commitlintDep = Boolean(deps["@commitlint/cli"] ?? deps["commitlint"]);
|
|
1965
1961
|
return {
|
|
@@ -2381,7 +2377,7 @@ var stateFreshnessLens = {
|
|
|
2381
2377
|
}
|
|
2382
2378
|
}
|
|
2383
2379
|
for (const a of stateArtifacts) {
|
|
2384
|
-
const text = await readText(
|
|
2380
|
+
const text = await readText(path2.join(ctx.root, a.path));
|
|
2385
2381
|
if (text === null) {
|
|
2386
2382
|
disclosures.push(`${a.path} could not be read \u2014 unexamined, not clean.`);
|
|
2387
2383
|
outOfScope.push(a.path);
|
|
@@ -2402,7 +2398,7 @@ var stateFreshnessLens = {
|
|
|
2402
2398
|
}
|
|
2403
2399
|
}
|
|
2404
2400
|
for (const a of decisionArtifacts) {
|
|
2405
|
-
const text = await readText(
|
|
2401
|
+
const text = await readText(path2.join(ctx.root, a.path));
|
|
2406
2402
|
if (text === null) {
|
|
2407
2403
|
disclosures.push(
|
|
2408
2404
|
`${a.path} recognized as a decisions convention (directory) \u2014 age-exempt; per-entry format checks apply only to marker-carrying decisions files.`
|
|
@@ -2455,7 +2451,7 @@ init_util();
|
|
|
2455
2451
|
async function listInstructionFiles(root, facts, scope) {
|
|
2456
2452
|
const files = [];
|
|
2457
2453
|
const add = async (rel) => {
|
|
2458
|
-
const text = await readText(
|
|
2454
|
+
const text = await readText(path2.join(root, rel));
|
|
2459
2455
|
if (text !== null) files.push({ path: normalizeRelPath(rel), text });
|
|
2460
2456
|
};
|
|
2461
2457
|
const singleFileArtifacts = [
|
|
@@ -2471,21 +2467,21 @@ async function listInstructionFiles(root, facts, scope) {
|
|
|
2471
2467
|
const artifact = facts.artifacts.find((a) => a.id === id);
|
|
2472
2468
|
if (artifact?.exists) await add(artifact.path);
|
|
2473
2469
|
}
|
|
2474
|
-
const rulesDir =
|
|
2470
|
+
const rulesDir = path2.join(root, ".cursor", "rules");
|
|
2475
2471
|
if (await isDirectory(rulesDir)) {
|
|
2476
2472
|
try {
|
|
2477
2473
|
for (const entry of await promises.readdir(rulesDir)) {
|
|
2478
2474
|
if (entry.endsWith(".md") || entry.endsWith(".mdc"))
|
|
2479
|
-
await add(
|
|
2475
|
+
await add(path2.join(".cursor/rules", entry));
|
|
2480
2476
|
}
|
|
2481
2477
|
} catch {
|
|
2482
2478
|
}
|
|
2483
2479
|
}
|
|
2484
|
-
const skillsDir =
|
|
2480
|
+
const skillsDir = path2.join(root, ".claude", "skills");
|
|
2485
2481
|
if (await isDirectory(skillsDir)) {
|
|
2486
2482
|
try {
|
|
2487
2483
|
for (const entry of await promises.readdir(skillsDir)) {
|
|
2488
|
-
const skill =
|
|
2484
|
+
const skill = path2.join(".claude/skills", entry, "SKILL.md");
|
|
2489
2485
|
await add(skill);
|
|
2490
2486
|
}
|
|
2491
2487
|
} catch {
|
|
@@ -2513,7 +2509,7 @@ async function listStateDocuments(root, facts) {
|
|
|
2513
2509
|
const docs = [];
|
|
2514
2510
|
for (const artifact of facts.artifacts) {
|
|
2515
2511
|
if (artifact.kind !== "state" || !artifact.exists) continue;
|
|
2516
|
-
const text = await readText(
|
|
2512
|
+
const text = await readText(path2.join(root, artifact.path));
|
|
2517
2513
|
if (text !== null) docs.push({ path: normalizeRelPath(artifact.path), text });
|
|
2518
2514
|
}
|
|
2519
2515
|
return docs;
|
|
@@ -2797,28 +2793,28 @@ var instructionTruthLens = {
|
|
|
2797
2793
|
const knownScripts = new Set(Object.keys(facts.commands.raw));
|
|
2798
2794
|
for (const pkg of facts.packages) {
|
|
2799
2795
|
const pkgJson = await readJson(
|
|
2800
|
-
|
|
2796
|
+
path2.join(root, pkg.dir, "package.json")
|
|
2801
2797
|
);
|
|
2802
2798
|
for (const key of Object.keys(pkgJson?.scripts ?? {})) knownScripts.add(key);
|
|
2803
2799
|
}
|
|
2804
2800
|
const pathResolves = async (claim) => {
|
|
2805
|
-
const bases = [root, ...facts.packages.map((p) =>
|
|
2801
|
+
const bases = [root, ...facts.packages.map((p) => path2.join(root, p.dir))];
|
|
2806
2802
|
for (const base of bases) {
|
|
2807
|
-
if (await pathExists(
|
|
2808
|
-
if (await pathExists(
|
|
2809
|
-
if (await pathExists(
|
|
2803
|
+
if (await pathExists(path2.join(base, claim))) return true;
|
|
2804
|
+
if (await pathExists(path2.join(base, "src", claim))) return true;
|
|
2805
|
+
if (await pathExists(path2.join(base, "scripts", claim))) return true;
|
|
2810
2806
|
}
|
|
2811
2807
|
return false;
|
|
2812
2808
|
};
|
|
2813
2809
|
const binResolves = async (name) => {
|
|
2814
|
-
const bases = [root, ...facts.packages.map((p) =>
|
|
2810
|
+
const bases = [root, ...facts.packages.map((p) => path2.join(root, p.dir))];
|
|
2815
2811
|
for (const base of bases) {
|
|
2816
|
-
if (await pathExists(
|
|
2812
|
+
if (await pathExists(path2.join(base, "node_modules", ".bin", name))) return true;
|
|
2817
2813
|
}
|
|
2818
2814
|
return false;
|
|
2819
2815
|
};
|
|
2820
|
-
const nodeModulesInstalled = await pathExists(
|
|
2821
|
-
const manifestExists = await pathExists(
|
|
2816
|
+
const nodeModulesInstalled = await pathExists(path2.join(root, "node_modules"));
|
|
2817
|
+
const manifestExists = await pathExists(path2.join(root, "package.json")) || facts.packages.length > 0;
|
|
2822
2818
|
let totalFilteredSkipped = 0;
|
|
2823
2819
|
let binaryResolved = 0;
|
|
2824
2820
|
let unverifiableCommands = 0;
|
|
@@ -2910,7 +2906,7 @@ var instructionTruthLens = {
|
|
|
2910
2906
|
}
|
|
2911
2907
|
}
|
|
2912
2908
|
for (const ref of extractDocRefs(file.text)) {
|
|
2913
|
-
if (await pathExists(
|
|
2909
|
+
if (await pathExists(path2.join(root, ref))) continue;
|
|
2914
2910
|
findings.push(
|
|
2915
2911
|
finding2({
|
|
2916
2912
|
id: `${LENS_ID4}/dangling-ref:${file.path}:${ref}`,
|
|
@@ -2934,7 +2930,7 @@ var instructionTruthLens = {
|
|
|
2934
2930
|
if (stateDocs.length) {
|
|
2935
2931
|
for (const artifact of facts.artifacts) {
|
|
2936
2932
|
if (artifact.kind !== "decisions" || !artifact.exists) continue;
|
|
2937
|
-
const text = await readText(
|
|
2933
|
+
const text = await readText(path2.join(root, artifact.path));
|
|
2938
2934
|
if (text === null) continue;
|
|
2939
2935
|
const entries = parseDecisionEntries(text);
|
|
2940
2936
|
if (!entries.length) continue;
|
|
@@ -3068,14 +3064,14 @@ function rankFindings(findings) {
|
|
|
3068
3064
|
// src/engine/ledger.ts
|
|
3069
3065
|
init_util();
|
|
3070
3066
|
function ledgerPath(root) {
|
|
3071
|
-
return
|
|
3067
|
+
return path2.join(root, ".etymd", "ledger.json");
|
|
3072
3068
|
}
|
|
3073
3069
|
async function readLedger(root) {
|
|
3074
3070
|
return await readJson(ledgerPath(root)) ?? { version: 1, entries: [] };
|
|
3075
3071
|
}
|
|
3076
3072
|
async function writeLedger(root, ledger) {
|
|
3077
3073
|
const target = ledgerPath(root);
|
|
3078
|
-
await promises.mkdir(
|
|
3074
|
+
await promises.mkdir(path2.dirname(target), { recursive: true });
|
|
3079
3075
|
await promises.writeFile(target, JSON.stringify(ledger, null, 2) + "\n", "utf8");
|
|
3080
3076
|
}
|
|
3081
3077
|
function belongsToAny(id, paths) {
|
|
@@ -3160,7 +3156,7 @@ var LENSES = [
|
|
|
3160
3156
|
async function runAudit(root, opts = {}) {
|
|
3161
3157
|
const facts = await scanProject(root, { upstreamRemote: opts.upstreamRemote });
|
|
3162
3158
|
const ledgerRoot = opts.ledgerRoot ?? root;
|
|
3163
|
-
if (!opts.readOnlyRoot && ((opts.persistLedger ?? true) || await pathExists(
|
|
3159
|
+
if (!opts.readOnlyRoot && ((opts.persistLedger ?? true) || await pathExists(path2.join(root, ETYMD_DIR)))) {
|
|
3164
3160
|
await writeCachedFacts(root, facts);
|
|
3165
3161
|
}
|
|
3166
3162
|
const baseline = await readBaseline(root);
|
|
@@ -3220,7 +3216,7 @@ function isFleetTrust(value) {
|
|
|
3220
3216
|
}
|
|
3221
3217
|
function expandTilde(p) {
|
|
3222
3218
|
if (p === "~") return os.homedir();
|
|
3223
|
-
if (p.startsWith("~/")) return
|
|
3219
|
+
if (p.startsWith("~/")) return path2.join(os.homedir(), p.slice(2));
|
|
3224
3220
|
return p;
|
|
3225
3221
|
}
|
|
3226
3222
|
var REBUILD_RECIPE = 'rebuild it beside the manifest: {"machineProfile":"corp","dirs":{"<name>":"~/path/to/worktree"}}';
|
|
@@ -3255,7 +3251,7 @@ async function readLocal(localPath, problems) {
|
|
|
3255
3251
|
if (!parsed) {
|
|
3256
3252
|
problems.push({
|
|
3257
3253
|
kind: "parse-error",
|
|
3258
|
-
file:
|
|
3254
|
+
file: path2.basename(localPath),
|
|
3259
3255
|
detail: "must contain a JSON object \u2014 local mappings unusable"
|
|
3260
3256
|
});
|
|
3261
3257
|
return { present: true, data: {} };
|
|
@@ -3264,7 +3260,7 @@ async function readLocal(localPath, problems) {
|
|
|
3264
3260
|
} catch (err) {
|
|
3265
3261
|
problems.push({
|
|
3266
3262
|
kind: "parse-error",
|
|
3267
|
-
file:
|
|
3263
|
+
file: path2.basename(localPath),
|
|
3268
3264
|
detail: `not valid JSON (${err instanceof Error ? err.message : String(err)}) \u2014 local mappings unusable`
|
|
3269
3265
|
});
|
|
3270
3266
|
return { present: true, data: {} };
|
|
@@ -3332,13 +3328,13 @@ function loadRegistryEntries(projects, manifest, manifestFile) {
|
|
|
3332
3328
|
} else {
|
|
3333
3329
|
const dir = manifest.localDirs[name];
|
|
3334
3330
|
if (!dir) {
|
|
3335
|
-
entry.unresolved = `no \`dirs\` mapping for \`${name}\` in ${
|
|
3331
|
+
entry.unresolved = `no \`dirs\` mapping for \`${name}\` in ${path2.basename(manifest.localPath)}`;
|
|
3336
3332
|
} else {
|
|
3337
|
-
entry.resolvedRoot =
|
|
3333
|
+
entry.resolvedRoot = path2.resolve(manifest.dir, expandTilde(dir));
|
|
3338
3334
|
}
|
|
3339
3335
|
}
|
|
3340
3336
|
} else if (entry.path) {
|
|
3341
|
-
entry.resolvedRoot =
|
|
3337
|
+
entry.resolvedRoot = path2.resolve(manifest.root ?? manifest.dir, expandTilde(entry.path));
|
|
3342
3338
|
} else {
|
|
3343
3339
|
entry.unresolved = "personal entry declares no `path` \u2014 nothing to resolve";
|
|
3344
3340
|
}
|
|
@@ -3347,7 +3343,7 @@ function loadRegistryEntries(projects, manifest, manifestFile) {
|
|
|
3347
3343
|
if (corpNames.length && !manifest.localPresent) {
|
|
3348
3344
|
manifest.problems.push({
|
|
3349
3345
|
kind: "local-missing",
|
|
3350
|
-
file:
|
|
3346
|
+
file: path2.basename(manifest.localPath),
|
|
3351
3347
|
detail: `${corpNames.length} corp/private entr${corpNames.length === 1 ? "y" : "ies"} (${corpNames.join(", ")}) cannot resolve without it \u2014 ${REBUILD_RECIPE}`
|
|
3352
3348
|
});
|
|
3353
3349
|
}
|
|
@@ -3376,19 +3372,19 @@ function loadCorpusEntries(sources, manifest, manifestFile) {
|
|
|
3376
3372
|
};
|
|
3377
3373
|
const localDir = manifest.localDirs[name];
|
|
3378
3374
|
if (localDir) {
|
|
3379
|
-
entry.resolvedRoot =
|
|
3375
|
+
entry.resolvedRoot = path2.resolve(manifest.dir, "..", expandTilde(localDir));
|
|
3380
3376
|
} else if (entry.path) {
|
|
3381
|
-
entry.resolvedRoot =
|
|
3377
|
+
entry.resolvedRoot = path2.resolve(manifest.dir, expandTilde(entry.path));
|
|
3382
3378
|
} else {
|
|
3383
|
-
entry.resolvedRoot =
|
|
3379
|
+
entry.resolvedRoot = path2.resolve(manifest.dir, "..", name);
|
|
3384
3380
|
}
|
|
3385
3381
|
manifest.entries.push(entry);
|
|
3386
3382
|
}
|
|
3387
3383
|
}
|
|
3388
3384
|
async function loadFleetManifest(manifestPath) {
|
|
3389
|
-
const abs =
|
|
3390
|
-
const dir =
|
|
3391
|
-
const manifestFile =
|
|
3385
|
+
const abs = path2.resolve(manifestPath);
|
|
3386
|
+
const dir = path2.dirname(abs);
|
|
3387
|
+
const manifestFile = path2.basename(abs);
|
|
3392
3388
|
const raw = await readText(abs);
|
|
3393
3389
|
if (raw === null) {
|
|
3394
3390
|
throw new Error(`cannot read fleet manifest \`${abs}\``);
|
|
@@ -3400,7 +3396,7 @@ async function loadFleetManifest(manifestPath) {
|
|
|
3400
3396
|
corpHosts: [],
|
|
3401
3397
|
labels: {},
|
|
3402
3398
|
localDirs: {},
|
|
3403
|
-
localPath:
|
|
3399
|
+
localPath: path2.join(dir, "registry.local.json"),
|
|
3404
3400
|
localPresent: false,
|
|
3405
3401
|
entries: [],
|
|
3406
3402
|
problems: []
|
|
@@ -3433,7 +3429,7 @@ async function loadFleetManifest(manifestPath) {
|
|
|
3433
3429
|
manifest.corpHosts = Array.isArray(local.data.corpHosts) ? local.data.corpHosts.filter((h) => typeof h === "string") : [];
|
|
3434
3430
|
manifest.machineProfile = local.data.machineProfile === "corp" || local.data.machineProfile === "personal" ? local.data.machineProfile : void 0;
|
|
3435
3431
|
const rootRaw = asString(local.data.root) ?? asString(parsed.root);
|
|
3436
|
-
if (rootRaw) manifest.root =
|
|
3432
|
+
if (rootRaw) manifest.root = path2.resolve(dir, expandTilde(rootRaw));
|
|
3437
3433
|
const orientationRec = asRecord(parsed.orientation);
|
|
3438
3434
|
if (orientationRec) {
|
|
3439
3435
|
const orientationRoot = asString(orientationRec.root);
|
|
@@ -3455,7 +3451,7 @@ async function loadFleetManifest(manifestPath) {
|
|
|
3455
3451
|
loadRegistryEntries(parsed.projects, manifest, manifestFile);
|
|
3456
3452
|
} else if (Array.isArray(parsed.sources)) {
|
|
3457
3453
|
manifest.shape = "corpus";
|
|
3458
|
-
manifest.localPath =
|
|
3454
|
+
manifest.localPath = path2.join(dir, "sources.local.json");
|
|
3459
3455
|
const local = await readLocal(manifest.localPath, manifest.problems);
|
|
3460
3456
|
manifest.localPresent = local.present;
|
|
3461
3457
|
manifest.localDirs = asStringMap(local.data.dirs);
|
|
@@ -3511,9 +3507,9 @@ function finding3(id, tier, claim, evidence, why, action) {
|
|
|
3511
3507
|
};
|
|
3512
3508
|
}
|
|
3513
3509
|
function corpPersistenceRoot(manifest, name) {
|
|
3514
|
-
const corpDir =
|
|
3515
|
-
const root =
|
|
3516
|
-
if (
|
|
3510
|
+
const corpDir = path2.join(manifest.dir, "corp");
|
|
3511
|
+
const root = path2.resolve(corpDir, name);
|
|
3512
|
+
if (path2.dirname(root) !== corpDir || root === corpDir) {
|
|
3517
3513
|
throw new Error(
|
|
3518
3514
|
`entry name ${JSON.stringify(name)} escapes the corp persistence zone \u2014 refusing to resolve a ledger root for it`
|
|
3519
3515
|
);
|
|
@@ -3545,7 +3541,7 @@ async function realpathOr(p) {
|
|
|
3545
3541
|
try {
|
|
3546
3542
|
return await promises.realpath(p);
|
|
3547
3543
|
} catch {
|
|
3548
|
-
return
|
|
3544
|
+
return path2.resolve(p);
|
|
3549
3545
|
}
|
|
3550
3546
|
}
|
|
3551
3547
|
function emailMatchesCorpHosts(email, hosts) {
|
|
@@ -3568,8 +3564,8 @@ var PROBLEM_TIER = {
|
|
|
3568
3564
|
async function checkManifest(manifest) {
|
|
3569
3565
|
const findings = [];
|
|
3570
3566
|
const disclosures = [];
|
|
3571
|
-
const manifestFile =
|
|
3572
|
-
const localFile =
|
|
3567
|
+
const manifestFile = path2.basename(manifest.manifestPath);
|
|
3568
|
+
const localFile = path2.basename(manifest.localPath);
|
|
3573
3569
|
for (const p of manifest.problems) {
|
|
3574
3570
|
findings.push(
|
|
3575
3571
|
finding3(
|
|
@@ -3721,7 +3717,7 @@ async function checkCorpWallArtifacts(entries, findings) {
|
|
|
3721
3717
|
for (const entry of entries) {
|
|
3722
3718
|
if (entry.profile !== "corp" || !entry.resolvedRoot) continue;
|
|
3723
3719
|
for (const artifact of CORP_WALL_ARTIFACTS) {
|
|
3724
|
-
if (await pathExists(
|
|
3720
|
+
if (await pathExists(path2.join(entry.resolvedRoot, artifact))) {
|
|
3725
3721
|
findings.push(
|
|
3726
3722
|
finding3(
|
|
3727
3723
|
`${FLEET_LENS}/corp-artifact-in-repo:${entry.name}:${artifact}`,
|
|
@@ -3755,9 +3751,9 @@ async function checkCoverage(manifest, findings, disclosures) {
|
|
|
3755
3751
|
const dirents = await promises.readdir(manifest.root, { withFileTypes: true });
|
|
3756
3752
|
for (const d of dirents) {
|
|
3757
3753
|
if (!d.isDirectory() || d.name.startsWith(".")) continue;
|
|
3758
|
-
const dir =
|
|
3754
|
+
const dir = path2.join(manifest.root, d.name);
|
|
3759
3755
|
if (registered.has(await realpathOr(dir))) continue;
|
|
3760
|
-
if (!await pathExists(
|
|
3756
|
+
if (!await pathExists(path2.join(dir, ".git"))) continue;
|
|
3761
3757
|
const remotes = await git(dir, ["config", "--get-regexp", "^remote\\..*\\.url"]);
|
|
3762
3758
|
if (!remotes) continue;
|
|
3763
3759
|
const corpRemote = manifest.corpHosts.find((h) => remotes.includes(h));
|
|
@@ -3816,7 +3812,7 @@ async function checkHygieneNeedles(manifest, findings, disclosures) {
|
|
|
3816
3812
|
...new Set(
|
|
3817
3813
|
[
|
|
3818
3814
|
...Object.values(manifest.labels),
|
|
3819
|
-
...Object.values(manifest.localDirs).map((d) =>
|
|
3815
|
+
...Object.values(manifest.localDirs).map((d) => path2.basename(expandNeedleDir(d))),
|
|
3820
3816
|
...manifest.corpHosts
|
|
3821
3817
|
].filter((n) => n.length >= 3)
|
|
3822
3818
|
)
|
|
@@ -4019,7 +4015,7 @@ async function sweepEntry(manifest, entry, opts) {
|
|
|
4019
4015
|
}
|
|
4020
4016
|
}
|
|
4021
4017
|
const corp = entry.profile === "corp";
|
|
4022
|
-
const optedIn = !corp && await pathExists(
|
|
4018
|
+
const optedIn = !corp && await pathExists(path2.join(root, ETYMD_DIR));
|
|
4023
4019
|
const result = await runAudit(root, {
|
|
4024
4020
|
kind: opts.kind,
|
|
4025
4021
|
persistLedger: !corp && opts.persistLedgers === true && optedIn,
|
|
@@ -4037,7 +4033,7 @@ async function sweepEntry(manifest, entry, opts) {
|
|
|
4037
4033
|
project.staleAfterDays = result.config.config.state.staleAfterDays;
|
|
4038
4034
|
for (const [key, value] of Object.entries(entry.contract)) {
|
|
4039
4035
|
if (key === "placement" || !value) continue;
|
|
4040
|
-
const exists = await pathExists(
|
|
4036
|
+
const exists = await pathExists(path2.join(root, value.replace(/#.*$/, "")));
|
|
4041
4037
|
project.disclosures.push(
|
|
4042
4038
|
`contract override \`${key}: ${value}\` \u2014 registered, existence-checked only (${exists ? "present" : "MISSING"})`
|
|
4043
4039
|
);
|