artifact-graph 0.8.4 → 0.9.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 +18 -0
- package/dist/cli.js +108 -81
- package/dist/index.cjs +147 -119
- package/dist/index.d.cts +2 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +95 -68
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.9.0
|
|
4
|
+
|
|
5
|
+
### Changed
|
|
6
|
+
|
|
7
|
+
- Version synchronized with `artifact-chain-assistant@0.9.0` (suite lockstep release). No runtime
|
|
8
|
+
behavior changes.
|
|
9
|
+
|
|
10
|
+
## 0.8.5
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
|
|
14
|
+
- E2E trace validation now accepts only real standalone line comments. Annotation-shaped text in
|
|
15
|
+
source strings and template literals is ignored, eliminating false `E2E-TRACE-002` findings from
|
|
16
|
+
test fixtures.
|
|
17
|
+
- Staged version-lock refresh now blocks only for unstaged files matched by the project's configured
|
|
18
|
+
artifact paths (plus the graph config and lock file). Unrelated Markdown or source files no longer
|
|
19
|
+
prevent a valid split commit, while genuine graph-relevant divergence remains fail-closed.
|
|
20
|
+
|
|
3
21
|
## 0.8.4
|
|
4
22
|
|
|
5
23
|
### Changed
|
package/dist/cli.js
CHANGED
|
@@ -316,6 +316,41 @@ var init_glob_matcher = __esm({
|
|
|
316
316
|
}
|
|
317
317
|
});
|
|
318
318
|
|
|
319
|
+
// src/file-walker.ts
|
|
320
|
+
import { readdir } from "fs/promises";
|
|
321
|
+
import { join, relative } from "path";
|
|
322
|
+
async function walkFiles(root, current = root, readDirectory = defaultReadDirectory) {
|
|
323
|
+
let entries;
|
|
324
|
+
try {
|
|
325
|
+
entries = await readDirectory(current, { withFileTypes: true });
|
|
326
|
+
} catch (error) {
|
|
327
|
+
if (current !== root && error.code === "ENOENT") {
|
|
328
|
+
return [];
|
|
329
|
+
}
|
|
330
|
+
throw error;
|
|
331
|
+
}
|
|
332
|
+
const files = [];
|
|
333
|
+
for (const entry of entries) {
|
|
334
|
+
if (entry.name === "node_modules" || entry.name === "dist" || entry.name === ".git" || entry.name === ".artifact-graph") {
|
|
335
|
+
continue;
|
|
336
|
+
}
|
|
337
|
+
const fullPath = join(current, entry.name);
|
|
338
|
+
if (entry.isDirectory()) {
|
|
339
|
+
files.push(...await walkFiles(root, fullPath, readDirectory));
|
|
340
|
+
} else {
|
|
341
|
+
files.push(relative(root, fullPath).split("\\").join("/"));
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
return files;
|
|
345
|
+
}
|
|
346
|
+
var defaultReadDirectory;
|
|
347
|
+
var init_file_walker = __esm({
|
|
348
|
+
"src/file-walker.ts"() {
|
|
349
|
+
"use strict";
|
|
350
|
+
defaultReadDirectory = readdir;
|
|
351
|
+
}
|
|
352
|
+
});
|
|
353
|
+
|
|
319
354
|
// src/target-selector.ts
|
|
320
355
|
function parseTargetSelector(value) {
|
|
321
356
|
const separator = value.indexOf(":");
|
|
@@ -830,7 +865,7 @@ var init_packet_assembler = __esm({
|
|
|
830
865
|
|
|
831
866
|
// src/packet-audit.ts
|
|
832
867
|
import { mkdir, writeFile } from "fs/promises";
|
|
833
|
-
import { join } from "path";
|
|
868
|
+
import { join as join2 } from "path";
|
|
834
869
|
function parseTargetsFile(content, schema) {
|
|
835
870
|
const validTypes = schema ? new Set(getTargetArtifactTypes(schema)) : VALID_TYPES;
|
|
836
871
|
const validTypesLabel = [...validTypes].join(", ");
|
|
@@ -921,7 +956,7 @@ async function auditSingleTarget(target, graph, options) {
|
|
|
921
956
|
const fmt = options.format ?? "markdown";
|
|
922
957
|
const ext = fmt === "json" ? "json" : "md";
|
|
923
958
|
const filename = `${target.type}-${target.id}.packet.${ext}`;
|
|
924
|
-
const outPath =
|
|
959
|
+
const outPath = join2(options.outDir, filename);
|
|
925
960
|
let content;
|
|
926
961
|
if (fmt === "json") {
|
|
927
962
|
content = JSON.stringify(packet, null, 2) + "\n";
|
|
@@ -1001,7 +1036,7 @@ async function auditPackets(root, targets, options, graph) {
|
|
|
1001
1036
|
...isCompact ? { summaryDetail: "compact", countsByType } : {}
|
|
1002
1037
|
};
|
|
1003
1038
|
if (options.outDir) {
|
|
1004
|
-
const summaryPath =
|
|
1039
|
+
const summaryPath = join2(options.outDir, "summary.json");
|
|
1005
1040
|
await writeFile(summaryPath, JSON.stringify(summary, null, 2) + "\n", "utf-8");
|
|
1006
1041
|
}
|
|
1007
1042
|
return summary;
|
|
@@ -1373,7 +1408,7 @@ var init_packet_prompt_validator = __esm({
|
|
|
1373
1408
|
import { createHash } from "crypto";
|
|
1374
1409
|
import { existsSync } from "fs";
|
|
1375
1410
|
import { mkdir as mkdir2, readFile, writeFile as writeFile2 } from "fs/promises";
|
|
1376
|
-
import { dirname, join as
|
|
1411
|
+
import { dirname, join as join3, relative as relative2 } from "path";
|
|
1377
1412
|
async function buildVersionIndex(root, graph) {
|
|
1378
1413
|
const scannedGraph = graph ?? await scanArtifacts(root);
|
|
1379
1414
|
const hashCache = /* @__PURE__ */ new Map();
|
|
@@ -1509,7 +1544,7 @@ async function auditVersionLock(root, lockPath = VERSION_LOCK_PATH, graph, confi
|
|
|
1509
1544
|
for (const entry of lock.locks) {
|
|
1510
1545
|
if (entry.kind !== "verifies") continue;
|
|
1511
1546
|
const sourcePath = entry.source.path;
|
|
1512
|
-
const fullSourcePath =
|
|
1547
|
+
const fullSourcePath = join3(root, sourcePath);
|
|
1513
1548
|
if (!existsSync(fullSourcePath)) continue;
|
|
1514
1549
|
let liveness = livenessCache.get(sourcePath);
|
|
1515
1550
|
if (liveness === void 0) {
|
|
@@ -1604,14 +1639,14 @@ async function auditVersionLock(root, lockPath = VERSION_LOCK_PATH, graph, confi
|
|
|
1604
1639
|
currentArtifactHash: targetNode.contentHash
|
|
1605
1640
|
});
|
|
1606
1641
|
}
|
|
1607
|
-
if (!existsSync(
|
|
1642
|
+
if (!existsSync(join3(root, sourceNode.path))) {
|
|
1608
1643
|
entryIssues.push({
|
|
1609
1644
|
status: "orphan_lock",
|
|
1610
1645
|
edgeId: relEdgeId,
|
|
1611
1646
|
message: `Artifact relation source file ${sourceNode.path} no longer exists`
|
|
1612
1647
|
});
|
|
1613
1648
|
}
|
|
1614
|
-
if (!existsSync(
|
|
1649
|
+
if (!existsSync(join3(root, targetNode.path))) {
|
|
1615
1650
|
entryIssues.push({
|
|
1616
1651
|
status: "orphan_lock",
|
|
1617
1652
|
edgeId: relEdgeId,
|
|
@@ -2184,7 +2219,7 @@ function renderTraceVersionMarkdown(result) {
|
|
|
2184
2219
|
async function readVersionLock(root, lockPath) {
|
|
2185
2220
|
const safeLockPath = normalizeRelativePath(root, lockPath);
|
|
2186
2221
|
try {
|
|
2187
|
-
const raw = await readFile(
|
|
2222
|
+
const raw = await readFile(join3(root, safeLockPath), "utf-8");
|
|
2188
2223
|
let parsed;
|
|
2189
2224
|
try {
|
|
2190
2225
|
parsed = JSON.parse(raw);
|
|
@@ -2343,7 +2378,7 @@ function requireSafeRelativePath(value, path) {
|
|
|
2343
2378
|
}
|
|
2344
2379
|
async function writeVersionLock(root, lockPath, lock) {
|
|
2345
2380
|
const safeLockPath = normalizeRelativePath(root, lockPath);
|
|
2346
|
-
const fullPath =
|
|
2381
|
+
const fullPath = join3(root, safeLockPath);
|
|
2347
2382
|
await mkdir2(dirname(fullPath), { recursive: true });
|
|
2348
2383
|
await writeFile2(fullPath, `${JSON.stringify(lock, null, 2)}
|
|
2349
2384
|
`);
|
|
@@ -2526,14 +2561,14 @@ async function hashRelativePath(root, path, cache) {
|
|
|
2526
2561
|
const normalized = normalizeRelativePath(root, path);
|
|
2527
2562
|
const cached = cache.get(normalized);
|
|
2528
2563
|
if (cached) return cached;
|
|
2529
|
-
const content = await readFile(
|
|
2564
|
+
const content = await readFile(join3(root, normalized));
|
|
2530
2565
|
const hash = `sha256:${createHash("sha256").update(content).digest("hex")}`;
|
|
2531
2566
|
cache.set(normalized, hash);
|
|
2532
2567
|
return hash;
|
|
2533
2568
|
}
|
|
2534
2569
|
function normalizeRelativePath(root, path) {
|
|
2535
2570
|
const normalized = path.replace(/\\/g, "/");
|
|
2536
|
-
const relativePath = normalized.startsWith("/") ?
|
|
2571
|
+
const relativePath = normalized.startsWith("/") ? relative2(root, normalized).replace(/\\/g, "/") : normalized.replace(/^\.\//, "");
|
|
2537
2572
|
if (relativePath === ".." || relativePath.startsWith("../")) {
|
|
2538
2573
|
throw new Error(`Path is outside root: ${path}`);
|
|
2539
2574
|
}
|
|
@@ -2549,7 +2584,7 @@ async function getTestFileRunnerLiveness(root, filePath, config) {
|
|
|
2549
2584
|
if (!/e2e/i.test(filePath) && !/\.e2e\./i.test(filePath)) {
|
|
2550
2585
|
return "active";
|
|
2551
2586
|
}
|
|
2552
|
-
const fullSourcePath =
|
|
2587
|
+
const fullSourcePath = join3(root, filePath);
|
|
2553
2588
|
if (!existsSync(fullSourcePath)) return "inactive";
|
|
2554
2589
|
try {
|
|
2555
2590
|
const content = await readFile(fullSourcePath, "utf-8");
|
|
@@ -2618,7 +2653,7 @@ var init_versioned_traceability = __esm({
|
|
|
2618
2653
|
import { execFile } from "child_process";
|
|
2619
2654
|
import { constants } from "fs";
|
|
2620
2655
|
import { access } from "fs/promises";
|
|
2621
|
-
import { isAbsolute, join as
|
|
2656
|
+
import { isAbsolute, join as join4, resolve } from "path";
|
|
2622
2657
|
import { promisify } from "util";
|
|
2623
2658
|
async function resolveArtifactGraphCli(root, options = {}) {
|
|
2624
2659
|
const pathCli = await findCommandOnPath("artifact-graph");
|
|
@@ -2626,7 +2661,7 @@ async function resolveArtifactGraphCli(root, options = {}) {
|
|
|
2626
2661
|
const candidates = [
|
|
2627
2662
|
{
|
|
2628
2663
|
source: "node_modules",
|
|
2629
|
-
path:
|
|
2664
|
+
path: join4(root, "node_modules/.bin/artifact-graph"),
|
|
2630
2665
|
exists: false
|
|
2631
2666
|
},
|
|
2632
2667
|
{
|
|
@@ -2659,8 +2694,8 @@ async function resolveArtifactGraphCli(root, options = {}) {
|
|
|
2659
2694
|
}
|
|
2660
2695
|
async function doctorArtifactChain(root, options = {}) {
|
|
2661
2696
|
const cli = await resolveArtifactGraphCli(root, options);
|
|
2662
|
-
const configPath =
|
|
2663
|
-
const lockPath =
|
|
2697
|
+
const configPath = join4(root, "artifact-graph.config.yaml");
|
|
2698
|
+
const lockPath = join4(root, VERSION_LOCK_PATH);
|
|
2664
2699
|
const supportedCommands = cli.path ? await detectSupportedCommands(cli.path) : [];
|
|
2665
2700
|
const nodeCompatible = isNodeCompatible(process.versions.node);
|
|
2666
2701
|
const warnings = [
|
|
@@ -2885,7 +2920,7 @@ var init_git_hook_path = __esm({
|
|
|
2885
2920
|
import { constants as constants2 } from "fs";
|
|
2886
2921
|
import { randomUUID } from "crypto";
|
|
2887
2922
|
import { lstat, mkdir as mkdir3, open, readlink, rename, unlink } from "fs/promises";
|
|
2888
|
-
import { basename, dirname as dirname2, join as
|
|
2923
|
+
import { basename, dirname as dirname2, join as join5 } from "path";
|
|
2889
2924
|
function detectHookInterpreter(content) {
|
|
2890
2925
|
if (content.trim().length === 0) {
|
|
2891
2926
|
return "empty";
|
|
@@ -3193,7 +3228,7 @@ async function removeHookAtomically(hookPath, snapshot) {
|
|
|
3193
3228
|
}
|
|
3194
3229
|
async function writeHookAtomically(hookPath, content, snapshot, mode) {
|
|
3195
3230
|
await mkdir3(dirname2(hookPath), { recursive: true });
|
|
3196
|
-
const temporaryPath =
|
|
3231
|
+
const temporaryPath = join5(dirname2(hookPath), `.${basename(hookPath)}.${randomUUID()}.tmp`);
|
|
3197
3232
|
let temporaryExists = false;
|
|
3198
3233
|
try {
|
|
3199
3234
|
const temporary = await open(temporaryPath, "wx", mode);
|
|
@@ -3658,8 +3693,8 @@ __export(contract_kernel_exports, {
|
|
|
3658
3693
|
verifyDigest: () => verifyDigest
|
|
3659
3694
|
});
|
|
3660
3695
|
import { createHash as createHash2 } from "crypto";
|
|
3661
|
-
import { readFile as readFile2, readdir } from "fs/promises";
|
|
3662
|
-
import { join as
|
|
3696
|
+
import { readFile as readFile2, readdir as readdir2 } from "fs/promises";
|
|
3697
|
+
import { join as join6 } from "path";
|
|
3663
3698
|
import _AjvModule from "ajv";
|
|
3664
3699
|
function isOfficialNamespace(namespace) {
|
|
3665
3700
|
return OFFICIAL_NAMESPACE_PATTERN.test(namespace);
|
|
@@ -4069,10 +4104,10 @@ async function loadContract(contractPath, options) {
|
|
|
4069
4104
|
}
|
|
4070
4105
|
async function loadContractsFromDirectory(contractsDir, options) {
|
|
4071
4106
|
const contracts = [];
|
|
4072
|
-
const entries = await
|
|
4107
|
+
const entries = await readdir2(contractsDir, { withFileTypes: true });
|
|
4073
4108
|
for (const entry of entries) {
|
|
4074
4109
|
if (entry.isDirectory()) {
|
|
4075
|
-
const schemaPath =
|
|
4110
|
+
const schemaPath = join6(contractsDir, entry.name, "schema.json");
|
|
4076
4111
|
const contract = await loadContract(schemaPath, options);
|
|
4077
4112
|
contracts.push(contract);
|
|
4078
4113
|
}
|
|
@@ -4564,8 +4599,8 @@ import Database from "better-sqlite3";
|
|
|
4564
4599
|
import matter from "gray-matter";
|
|
4565
4600
|
import yaml from "js-yaml";
|
|
4566
4601
|
import { accessSync, constants as fsConstants, existsSync as existsSync2, statSync } from "fs";
|
|
4567
|
-
import { mkdir as mkdir4, readFile as readFile3, readdir as
|
|
4568
|
-
import { basename as basename3, dirname as dirname3, extname, isAbsolute as isAbsolute3, join as
|
|
4602
|
+
import { mkdir as mkdir4, readFile as readFile3, readdir as readdir3, writeFile as writeFile3 } from "fs/promises";
|
|
4603
|
+
import { basename as basename3, dirname as dirname3, extname, isAbsolute as isAbsolute3, join as join7, relative as relative3, resolve as resolve3 } from "path";
|
|
4569
4604
|
function isTargetArtifactType(type) {
|
|
4570
4605
|
return isPacketTargetType(type);
|
|
4571
4606
|
}
|
|
@@ -4607,7 +4642,7 @@ function resolveArtifactTypeName(schema, token) {
|
|
|
4607
4642
|
return void 0;
|
|
4608
4643
|
}
|
|
4609
4644
|
async function loadConfig(root) {
|
|
4610
|
-
const configPath =
|
|
4645
|
+
const configPath = join7(root, "artifact-graph.config.yaml");
|
|
4611
4646
|
let parsed = {};
|
|
4612
4647
|
try {
|
|
4613
4648
|
const raw = await readFile3(configPath, "utf-8");
|
|
@@ -4784,7 +4819,7 @@ async function scanArtifacts(root, schema) {
|
|
|
4784
4819
|
continue;
|
|
4785
4820
|
}
|
|
4786
4821
|
scannedFiles.set(file, type);
|
|
4787
|
-
const raw = await readFile3(
|
|
4822
|
+
const raw = await readFile3(join7(root, file), "utf-8");
|
|
4788
4823
|
const parsed = parseFile(type, file, raw, config);
|
|
4789
4824
|
nodes.push(...parsed.nodes);
|
|
4790
4825
|
edges.push(...parsed.edges);
|
|
@@ -5096,7 +5131,7 @@ async function validateScenarioPrdLinkIndex(root, graph) {
|
|
|
5096
5131
|
const indexPath = "artifacts/prd/feature-index.md";
|
|
5097
5132
|
let raw = "";
|
|
5098
5133
|
try {
|
|
5099
|
-
raw = await readFile3(
|
|
5134
|
+
raw = await readFile3(join7(root, indexPath), "utf-8");
|
|
5100
5135
|
} catch (error) {
|
|
5101
5136
|
if (error.code === "ENOENT") {
|
|
5102
5137
|
return [];
|
|
@@ -5286,11 +5321,11 @@ function nextId(graph, schema, type, rangeName) {
|
|
|
5286
5321
|
throw new Error(`ID range ${type}.${rangeName} is exhausted`);
|
|
5287
5322
|
}
|
|
5288
5323
|
async function writeGraphCache(root, graph) {
|
|
5289
|
-
const cacheDir =
|
|
5324
|
+
const cacheDir = join7(root, ".artifact-graph");
|
|
5290
5325
|
await mkdir4(cacheDir, { recursive: true });
|
|
5291
|
-
await writeFile3(
|
|
5326
|
+
await writeFile3(join7(cacheDir, "index.json"), `${JSON.stringify(graph, null, 2)}
|
|
5292
5327
|
`);
|
|
5293
|
-
const db = new Database(
|
|
5328
|
+
const db = new Database(join7(cacheDir, "graph.sqlite"));
|
|
5294
5329
|
try {
|
|
5295
5330
|
db.exec(`
|
|
5296
5331
|
DROP TABLE IF EXISTS nodes;
|
|
@@ -6973,10 +7008,10 @@ function validateE2eRegistry(graph) {
|
|
|
6973
7008
|
async function validateExecutableTraceability(root, config) {
|
|
6974
7009
|
const issues = [];
|
|
6975
7010
|
const schema = config ?? await loadConfig(root);
|
|
6976
|
-
const e2eDir =
|
|
7011
|
+
const e2eDir = join7(root, "artifacts", "tests", "e2e");
|
|
6977
7012
|
let e2eFiles;
|
|
6978
7013
|
try {
|
|
6979
|
-
e2eFiles = (await
|
|
7014
|
+
e2eFiles = (await readdir3(e2eDir)).filter((name) => /^test-.*\.md$/.test(name)).map((name) => join7(e2eDir, name));
|
|
6980
7015
|
} catch {
|
|
6981
7016
|
return [];
|
|
6982
7017
|
}
|
|
@@ -6986,7 +7021,7 @@ async function validateExecutableTraceability(root, config) {
|
|
|
6986
7021
|
const mdBatches = /* @__PURE__ */ new Set();
|
|
6987
7022
|
for (const filePath of e2eFiles) {
|
|
6988
7023
|
const raw = await readFile3(filePath, "utf-8");
|
|
6989
|
-
const relPath =
|
|
7024
|
+
const relPath = relative3(root, filePath).split("\\").join("/");
|
|
6990
7025
|
const parsed = matter(raw);
|
|
6991
7026
|
const data = parsed.data;
|
|
6992
7027
|
const batch = String(data.test_batch ?? basename3(filePath, extname(filePath))).trim();
|
|
@@ -7014,7 +7049,7 @@ async function validateExecutableTraceability(root, config) {
|
|
|
7014
7049
|
}
|
|
7015
7050
|
}
|
|
7016
7051
|
}
|
|
7017
|
-
const allFiles = await
|
|
7052
|
+
const allFiles = await walkFiles(root);
|
|
7018
7053
|
const specFiles = /* @__PURE__ */ new Set();
|
|
7019
7054
|
const configuredRunners = schema.e2e?.runners ?? [];
|
|
7020
7055
|
if (configuredRunners.length > 0) {
|
|
@@ -7034,7 +7069,7 @@ async function validateExecutableTraceability(root, config) {
|
|
|
7034
7069
|
const tcAnnotationRegex = /\/\/!?\s*@(?:e2e_test|tc)\s+(\S+?)\s+\[(\w+)\]/;
|
|
7035
7070
|
const tcAnnotationNoLevelRegex = /\/\/!?\s*@(?:e2e_test|tc)\s+(\S+)/;
|
|
7036
7071
|
for (const specFile of specFiles) {
|
|
7037
|
-
const fullSpecPath =
|
|
7072
|
+
const fullSpecPath = join7(root, specFile);
|
|
7038
7073
|
let content;
|
|
7039
7074
|
try {
|
|
7040
7075
|
content = await readFile3(fullSpecPath, "utf-8");
|
|
@@ -7043,14 +7078,16 @@ async function validateExecutableTraceability(root, config) {
|
|
|
7043
7078
|
}
|
|
7044
7079
|
const level = detectTestLevel(specFile, content);
|
|
7045
7080
|
const specLines = content.split(/\r?\n/);
|
|
7046
|
-
|
|
7047
|
-
|
|
7048
|
-
|
|
7081
|
+
const lineComments = scanCodeComments(content).filter((comment) => comment.kind === "line" && comment.standalone);
|
|
7082
|
+
for (const comment of lineComments) {
|
|
7083
|
+
const lineIndex = comment.lineNumber - 1;
|
|
7084
|
+
const commentText = comment.text.replace(/^!/, "");
|
|
7085
|
+
let match = tcAnnotationRegex.exec(`//${commentText}`);
|
|
7049
7086
|
let annotatedLevel = "";
|
|
7050
7087
|
if (match) {
|
|
7051
7088
|
annotatedLevel = match[2];
|
|
7052
7089
|
} else {
|
|
7053
|
-
match = tcAnnotationNoLevelRegex.exec(
|
|
7090
|
+
match = tcAnnotationNoLevelRegex.exec(`//${commentText}`);
|
|
7054
7091
|
}
|
|
7055
7092
|
if (!match) {
|
|
7056
7093
|
continue;
|
|
@@ -7077,7 +7114,7 @@ async function validateExecutableTraceability(root, config) {
|
|
|
7077
7114
|
level: effectiveLevel,
|
|
7078
7115
|
file: specFile,
|
|
7079
7116
|
testName,
|
|
7080
|
-
line:
|
|
7117
|
+
line: comment.lineNumber
|
|
7081
7118
|
};
|
|
7082
7119
|
const key = `${batch}:${tcId}`;
|
|
7083
7120
|
const existing = refToSource.get(key) ?? [];
|
|
@@ -7114,7 +7151,7 @@ async function validateExecutableTraceability(root, config) {
|
|
|
7114
7151
|
if (entry.testId) {
|
|
7115
7152
|
let content;
|
|
7116
7153
|
try {
|
|
7117
|
-
content = await readFile3(
|
|
7154
|
+
content = await readFile3(join7(root, normalizedRefFile), "utf-8");
|
|
7118
7155
|
} catch {
|
|
7119
7156
|
continue;
|
|
7120
7157
|
}
|
|
@@ -7291,11 +7328,11 @@ async function computeE2eCoverageStats(graph, root, thresholds = {}) {
|
|
|
7291
7328
|
let withExecutableRef = 0;
|
|
7292
7329
|
const statusBreakdown = {};
|
|
7293
7330
|
const chainTypeBreakdown = {};
|
|
7294
|
-
const e2eDir =
|
|
7331
|
+
const e2eDir = join7(root, "artifacts", "tests", "e2e");
|
|
7295
7332
|
const tcFieldsMap = /* @__PURE__ */ new Map();
|
|
7296
7333
|
let e2eFiles;
|
|
7297
7334
|
try {
|
|
7298
|
-
e2eFiles = (await
|
|
7335
|
+
e2eFiles = (await readdir3(e2eDir)).filter((name) => /^test-.*\.md$/.test(name)).map((name) => join7(e2eDir, name));
|
|
7299
7336
|
} catch {
|
|
7300
7337
|
e2eFiles = [];
|
|
7301
7338
|
}
|
|
@@ -7362,7 +7399,7 @@ async function computeE2eCoverageStats(graph, root, thresholds = {}) {
|
|
|
7362
7399
|
}
|
|
7363
7400
|
}
|
|
7364
7401
|
const runners = (await loadConfig(root)).e2e?.runners ?? [];
|
|
7365
|
-
const allProjectFiles = await
|
|
7402
|
+
const allProjectFiles = await walkFiles(root);
|
|
7366
7403
|
for (const node of e2eNodes) {
|
|
7367
7404
|
const fields = tcFieldsMap.get(node.code) ?? asRecord(node.attrs?.tcFields);
|
|
7368
7405
|
const status = String(fields["status"] ?? "").trim().toLowerCase();
|
|
@@ -7373,7 +7410,7 @@ async function computeE2eCoverageStats(graph, root, thresholds = {}) {
|
|
|
7373
7410
|
let hasActiveE2eRef = false;
|
|
7374
7411
|
for (const entry of parseExecutableRefLines(execRef)) {
|
|
7375
7412
|
const normalized = resolveExecutableRefFile(entry.file, allProjectFiles);
|
|
7376
|
-
if (!normalized || !existsSync2(
|
|
7413
|
+
if (!normalized || !existsSync2(join7(root, normalized))) continue;
|
|
7377
7414
|
const accepting = await getAcceptingRunners(root, normalized, runners);
|
|
7378
7415
|
if (accepting.some((runner) => runner.kind === "e2e")) {
|
|
7379
7416
|
hasActiveE2eRef = true;
|
|
@@ -7432,7 +7469,7 @@ async function computeE2eCoverageStats(graph, root, thresholds = {}) {
|
|
|
7432
7469
|
const acCoverageRateByFeature = {};
|
|
7433
7470
|
const featureAcMap = /* @__PURE__ */ new Map();
|
|
7434
7471
|
for (const node of featureNodes) {
|
|
7435
|
-
const acs = parseAcceptanceCriteria(await readFile3(
|
|
7472
|
+
const acs = parseAcceptanceCriteria(await readFile3(join7(root, node.path), "utf-8"));
|
|
7436
7473
|
featureAcMap.set(node.code, new Set(acs));
|
|
7437
7474
|
}
|
|
7438
7475
|
const coveredAcByFeature = /* @__PURE__ */ new Map();
|
|
@@ -7474,10 +7511,10 @@ async function computeE2eCoverageStats(graph, root, thresholds = {}) {
|
|
|
7474
7511
|
};
|
|
7475
7512
|
}
|
|
7476
7513
|
async function generateE2eRegistry(root, opts) {
|
|
7477
|
-
const e2eDir =
|
|
7514
|
+
const e2eDir = join7(root, "artifacts", "tests", "e2e");
|
|
7478
7515
|
let files;
|
|
7479
7516
|
try {
|
|
7480
|
-
files = (await
|
|
7517
|
+
files = (await readdir3(e2eDir)).filter((name) => /^test-.*\.md$/.test(name)).sort();
|
|
7481
7518
|
} catch {
|
|
7482
7519
|
return {
|
|
7483
7520
|
registry_version: "1.0",
|
|
@@ -7490,7 +7527,7 @@ async function generateE2eRegistry(root, opts) {
|
|
|
7490
7527
|
const batches = [];
|
|
7491
7528
|
let totalTestCases = 0;
|
|
7492
7529
|
for (const file of files) {
|
|
7493
|
-
const filePath =
|
|
7530
|
+
const filePath = join7(e2eDir, file);
|
|
7494
7531
|
const raw = await readFile3(filePath, "utf-8");
|
|
7495
7532
|
const parsed = matter(raw);
|
|
7496
7533
|
const data = parsed.data;
|
|
@@ -7618,7 +7655,7 @@ async function validatePartialRustEvidence(tcFields, tcKey, root, allFiles) {
|
|
|
7618
7655
|
if (!normalizedPath) {
|
|
7619
7656
|
return { hasValidPartialRust: false, detail: `partial_rust file not found: ${ref.file}` };
|
|
7620
7657
|
}
|
|
7621
|
-
const fullPath =
|
|
7658
|
+
const fullPath = join7(root, normalizedPath);
|
|
7622
7659
|
let content;
|
|
7623
7660
|
try {
|
|
7624
7661
|
content = await readFile3(fullPath, "utf-8");
|
|
@@ -7730,7 +7767,7 @@ function escapeRegExp(value) {
|
|
|
7730
7767
|
}
|
|
7731
7768
|
async function hasMarkdownTc(tcKey, e2eDir) {
|
|
7732
7769
|
const [batch, tcId] = tcKey.split(":");
|
|
7733
|
-
const filePath =
|
|
7770
|
+
const filePath = join7(e2eDir, `${batch}.md`);
|
|
7734
7771
|
try {
|
|
7735
7772
|
const raw = await readFile3(filePath, "utf-8");
|
|
7736
7773
|
const tcRegex = new RegExp(`^#{2,3}\\s+${escapeRegExp(tcId)}\\s*[:\uFF1A]?`, "m");
|
|
@@ -7740,7 +7777,7 @@ async function hasMarkdownTc(tcKey, e2eDir) {
|
|
|
7740
7777
|
}
|
|
7741
7778
|
}
|
|
7742
7779
|
async function findFiles(root, patterns) {
|
|
7743
|
-
const all = await
|
|
7780
|
+
const all = await walkFiles(root);
|
|
7744
7781
|
const matched = /* @__PURE__ */ new Set();
|
|
7745
7782
|
for (const pattern of patterns) {
|
|
7746
7783
|
for (const file of all) {
|
|
@@ -7751,21 +7788,9 @@ async function findFiles(root, patterns) {
|
|
|
7751
7788
|
}
|
|
7752
7789
|
return [...matched].sort();
|
|
7753
7790
|
}
|
|
7754
|
-
|
|
7755
|
-
const
|
|
7756
|
-
|
|
7757
|
-
for (const entry of entries) {
|
|
7758
|
-
if (entry.name === "node_modules" || entry.name === "dist" || entry.name === ".git" || entry.name === ".artifact-graph") {
|
|
7759
|
-
continue;
|
|
7760
|
-
}
|
|
7761
|
-
const fullPath = join6(current, entry.name);
|
|
7762
|
-
if (entry.isDirectory()) {
|
|
7763
|
-
files.push(...await walk(root, fullPath));
|
|
7764
|
-
} else {
|
|
7765
|
-
files.push(relative2(root, fullPath).split("\\").join("/"));
|
|
7766
|
-
}
|
|
7767
|
-
}
|
|
7768
|
-
return files;
|
|
7791
|
+
function matchesConfiguredArtifactPath(path, schema) {
|
|
7792
|
+
const normalizedPath = path.replace(/\\/g, "/").replace(/^\.\//, "");
|
|
7793
|
+
return Object.values(schema.types).some((definition) => definition.paths.some((pattern) => matchesPattern(normalizedPath, pattern)));
|
|
7769
7794
|
}
|
|
7770
7795
|
function matchesPattern(file, pattern) {
|
|
7771
7796
|
if (!pattern.includes("*")) {
|
|
@@ -8242,7 +8267,7 @@ function resolveArtifactContext(graph, opts) {
|
|
|
8242
8267
|
}
|
|
8243
8268
|
if (root) {
|
|
8244
8269
|
for (const ap of ALWAYS_PRESENT_ITEMS) {
|
|
8245
|
-
const fullPath =
|
|
8270
|
+
const fullPath = join7(root, ap.path);
|
|
8246
8271
|
let stat;
|
|
8247
8272
|
try {
|
|
8248
8273
|
stat = statSync(fullPath);
|
|
@@ -8479,6 +8504,7 @@ var init_index = __esm({
|
|
|
8479
8504
|
init_packet_constants();
|
|
8480
8505
|
init_packet_validator();
|
|
8481
8506
|
init_glob_matcher();
|
|
8507
|
+
init_file_walker();
|
|
8482
8508
|
init_packet_constants();
|
|
8483
8509
|
init_target_selector();
|
|
8484
8510
|
init_packet_assembler();
|
|
@@ -8583,7 +8609,7 @@ var init_index = __esm({
|
|
|
8583
8609
|
|
|
8584
8610
|
// src/packet-prompt-audit.ts
|
|
8585
8611
|
import { mkdir as mkdir5, writeFile as writeFile4 } from "fs/promises";
|
|
8586
|
-
import { join as
|
|
8612
|
+
import { join as join8 } from "path";
|
|
8587
8613
|
function promptFilename(target) {
|
|
8588
8614
|
return `prompt-${target.type}-${target.id}.md`;
|
|
8589
8615
|
}
|
|
@@ -8635,7 +8661,7 @@ async function auditSinglePromptTarget(target, graph, options) {
|
|
|
8635
8661
|
}
|
|
8636
8662
|
if (options.outDir) {
|
|
8637
8663
|
const filename = promptFilename(target);
|
|
8638
|
-
const outPath =
|
|
8664
|
+
const outPath = join8(options.outDir, filename);
|
|
8639
8665
|
await writeFile4(outPath, prompt, "utf-8");
|
|
8640
8666
|
entry.outputPath = outPath;
|
|
8641
8667
|
}
|
|
@@ -8741,9 +8767,9 @@ async function auditPromptBatch(root, targets, options, graph) {
|
|
|
8741
8767
|
};
|
|
8742
8768
|
if (options.outDir) {
|
|
8743
8769
|
await mkdir5(options.outDir, { recursive: true });
|
|
8744
|
-
const jsonPath =
|
|
8770
|
+
const jsonPath = join8(options.outDir, "prompt-audit-summary.json");
|
|
8745
8771
|
await writeFile4(jsonPath, JSON.stringify(summary, null, 2) + "\n", "utf-8");
|
|
8746
|
-
const mdPath =
|
|
8772
|
+
const mdPath = join8(options.outDir, "prompt-audit-summary.md");
|
|
8747
8773
|
await writeFile4(mdPath, renderPromptAuditSummaryMarkdown(summary), "utf-8");
|
|
8748
8774
|
}
|
|
8749
8775
|
return summary;
|
|
@@ -8785,7 +8811,7 @@ __export(cli_exports, {
|
|
|
8785
8811
|
import yaml2 from "js-yaml";
|
|
8786
8812
|
import { realpathSync } from "fs";
|
|
8787
8813
|
import { access as access2, mkdir as mkdir6, readFile as readFile4, writeFile as writeFile5 } from "fs/promises";
|
|
8788
|
-
import { dirname as dirname4, isAbsolute as isAbsolute4, join as
|
|
8814
|
+
import { dirname as dirname4, isAbsolute as isAbsolute4, join as join9 } from "path";
|
|
8789
8815
|
import { fileURLToPath } from "url";
|
|
8790
8816
|
async function runCli(argv, io = {}) {
|
|
8791
8817
|
const parsed = parseArgs(argv);
|
|
@@ -8802,7 +8828,7 @@ async function runCli(argv, io = {}) {
|
|
|
8802
8828
|
switch (parsed.command) {
|
|
8803
8829
|
case "init": {
|
|
8804
8830
|
await initConfig(root);
|
|
8805
|
-
out(`Created ${
|
|
8831
|
+
out(`Created ${join9(root, "artifact-graph.config.yaml")}
|
|
8806
8832
|
`);
|
|
8807
8833
|
return 0;
|
|
8808
8834
|
}
|
|
@@ -9631,7 +9657,8 @@ async function runCli(argv, io = {}) {
|
|
|
9631
9657
|
err("Stage or stash the unstaged changes before running changed-only staged refresh.\n");
|
|
9632
9658
|
return 1;
|
|
9633
9659
|
}
|
|
9634
|
-
const
|
|
9660
|
+
const schema = await loadConfig(root);
|
|
9661
|
+
const unstagedGraphPaths = changeResult.unstagedPaths.filter((path) => isGraphRelevantPath(path, schema));
|
|
9635
9662
|
if (unstagedGraphPaths.length > 0) {
|
|
9636
9663
|
err("Cannot refresh staged version locks because graph-relevant unstaged changes may affect working-tree hashes:\n");
|
|
9637
9664
|
for (const conflictPath of unstagedGraphPaths) {
|
|
@@ -9740,7 +9767,7 @@ async function runCli(argv, io = {}) {
|
|
|
9740
9767
|
err("Usage: artifact-graph validate-review-result --file <path> [--format json]\n");
|
|
9741
9768
|
return 1;
|
|
9742
9769
|
}
|
|
9743
|
-
const resolvedPath = isAbsolute4(filePath) ? filePath :
|
|
9770
|
+
const resolvedPath = isAbsolute4(filePath) ? filePath : join9(root, filePath);
|
|
9744
9771
|
let content;
|
|
9745
9772
|
try {
|
|
9746
9773
|
content = await readFile4(resolvedPath, "utf-8");
|
|
@@ -9778,7 +9805,7 @@ async function runCli(argv, io = {}) {
|
|
|
9778
9805
|
const deterministic = checkMode || parsed.flags.deterministic === true;
|
|
9779
9806
|
const registry = await generateE2eRegistry(root, { deterministic });
|
|
9780
9807
|
const output = JSON.stringify(registry, null, 2) + "\n";
|
|
9781
|
-
const outPath = typeof parsed.flags.out === "string" ? parsed.flags.out :
|
|
9808
|
+
const outPath = typeof parsed.flags.out === "string" ? parsed.flags.out : join9(root, "artifacts/tests/e2e/e2e-test-registry.json");
|
|
9782
9809
|
if (checkMode) {
|
|
9783
9810
|
let existing = "";
|
|
9784
9811
|
try {
|
|
@@ -9815,7 +9842,7 @@ async function runCli(argv, io = {}) {
|
|
|
9815
9842
|
return 1;
|
|
9816
9843
|
}
|
|
9817
9844
|
const packageDir = dirname4(fileURLToPath(import.meta.url));
|
|
9818
|
-
const contractsDir = typeof parsed.flags["contracts-dir"] === "string" ? parsed.flags["contracts-dir"] :
|
|
9845
|
+
const contractsDir = typeof parsed.flags["contracts-dir"] === "string" ? parsed.flags["contracts-dir"] : join9(packageDir, "..", "contracts");
|
|
9819
9846
|
const revisionDigest = typeof parsed.flags["revision-digest"] === "string" ? parsed.flags["revision-digest"] : void 0;
|
|
9820
9847
|
async function resolveContract(contractId) {
|
|
9821
9848
|
const catalog = await loadContractCatalog(contractsDir);
|
|
@@ -10078,11 +10105,11 @@ function hasBlockingVersionIssues(issues, strictMissingLock) {
|
|
|
10078
10105
|
severity: issue2.severity
|
|
10079
10106
|
}, strictMissingLock));
|
|
10080
10107
|
}
|
|
10081
|
-
function isGraphRelevantPath(path) {
|
|
10082
|
-
return path === "artifact-graph.config.yaml" || path === VERSION_LOCK_PATH || path
|
|
10108
|
+
function isGraphRelevantPath(path, schema) {
|
|
10109
|
+
return path === "artifact-graph.config.yaml" || path === VERSION_LOCK_PATH || matchesConfiguredArtifactPath(path, schema);
|
|
10083
10110
|
}
|
|
10084
10111
|
async function initConfig(root) {
|
|
10085
|
-
const configPath =
|
|
10112
|
+
const configPath = join9(root, "artifact-graph.config.yaml");
|
|
10086
10113
|
try {
|
|
10087
10114
|
await access2(configPath);
|
|
10088
10115
|
throw new Error(`Config already exists: ${configPath}`);
|