@aiwg/cli 2026.8.15 → 2026.8.16
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.
|
@@ -17,30 +17,10 @@ import { DEFAULT_INDEX_EXTENSIONS, INDEX_EXTRACTOR_VERSION, INDEX_VERSION, INDEX
|
|
|
17
17
|
import { parseCitationSidecar, citationResultToEdges, buildRefToPathMap } from './citation-parser.js';
|
|
18
18
|
import { writeIndexFile, resolveIndexDir, loadGraphIndexFile } from './index-reader.js';
|
|
19
19
|
import { loadManifest, writeManifest, statMatches, makeEntry } from './checksum-manifest.js';
|
|
20
|
-
import { workspaceLinkedFiles } from '../smiths/context-pipeline/workspace-context.js';
|
|
21
20
|
import { normalizeOperationalState } from './operational-state.js';
|
|
22
21
|
import { DEFAULT_PROJECT_AIWG_DIR, resolveProjectAiwgDir, } from '../config/project-artifacts.js';
|
|
23
22
|
import { normalizeStateTransferProjection } from './state-transfer.js';
|
|
24
|
-
|
|
25
|
-
const relative = path.relative(parent, child);
|
|
26
|
-
return relative === '' || (!relative.startsWith('..') && !path.isAbsolute(relative));
|
|
27
|
-
}
|
|
28
|
-
function toPosixPath(value) {
|
|
29
|
-
return value.split(path.sep).join('/');
|
|
30
|
-
}
|
|
31
|
-
function indexPathFor(cwd, fullPath, graph) {
|
|
32
|
-
if (!graph || graph === 'project') {
|
|
33
|
-
const artifactRoot = resolveProjectAiwgDir(cwd);
|
|
34
|
-
if (pathContains(artifactRoot, fullPath)) {
|
|
35
|
-
const relative = toPosixPath(path.relative(artifactRoot, fullPath));
|
|
36
|
-
return relative ? `${DEFAULT_PROJECT_AIWG_DIR}/${relative}` : DEFAULT_PROJECT_AIWG_DIR;
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
const rel = path.relative(cwd, fullPath);
|
|
40
|
-
if (!rel.startsWith('..') && !path.isAbsolute(rel))
|
|
41
|
-
return toPosixPath(rel);
|
|
42
|
-
return fullPath;
|
|
43
|
-
}
|
|
23
|
+
import { collectGraphIndexFiles, findArtifactFiles, indexPathFor } from './index-files.js';
|
|
44
24
|
function absoluteEntryPath(cwd, entryPath, graph) {
|
|
45
25
|
if ((!graph || graph === 'project') && entryPath.startsWith(`${DEFAULT_PROJECT_AIWG_DIR}/`)) {
|
|
46
26
|
return path.join(resolveProjectAiwgDir(cwd), entryPath.slice(DEFAULT_PROJECT_AIWG_DIR.length + 1));
|
|
@@ -587,31 +567,6 @@ function applyMetadataSupplements(entries, supplements, cwd) {
|
|
|
587
567
|
}
|
|
588
568
|
}
|
|
589
569
|
}
|
|
590
|
-
/**
|
|
591
|
-
* Recursively find all indexable files under a directory
|
|
592
|
-
*/
|
|
593
|
-
function findArtifactFiles(dir, extensions = [...DEFAULT_INDEX_EXTENSIONS]) {
|
|
594
|
-
const results = [];
|
|
595
|
-
if (!fs.existsSync(dir))
|
|
596
|
-
return results;
|
|
597
|
-
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
598
|
-
for (const entry of entries) {
|
|
599
|
-
const fullPath = path.join(dir, entry.name);
|
|
600
|
-
if (entry.isSymbolicLink() && !fs.existsSync(fullPath)) {
|
|
601
|
-
continue;
|
|
602
|
-
}
|
|
603
|
-
if (entry.isDirectory()) {
|
|
604
|
-
// Skip hidden dirs and .index
|
|
605
|
-
if (entry.name.startsWith('.'))
|
|
606
|
-
continue;
|
|
607
|
-
results.push(...findArtifactFiles(fullPath, extensions));
|
|
608
|
-
}
|
|
609
|
-
else if (extensions.some(ext => entry.name.endsWith(ext))) {
|
|
610
|
-
results.push(fullPath);
|
|
611
|
-
}
|
|
612
|
-
}
|
|
613
|
-
return results;
|
|
614
|
-
}
|
|
615
570
|
/**
|
|
616
571
|
* Build the artifact index
|
|
617
572
|
*/
|
|
@@ -798,24 +753,9 @@ export async function buildIndex(cwd, options = {}) {
|
|
|
798
753
|
pruned: 0,
|
|
799
754
|
};
|
|
800
755
|
// Collect files from all scan directories
|
|
801
|
-
const files =
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
}
|
|
805
|
-
// WORKSPACE.md is the root of the project context graph. Index it and its
|
|
806
|
-
// local Markdown-linked nodes without copying them into provider trees.
|
|
807
|
-
if (!scope && (!graph || graph === 'project')) {
|
|
808
|
-
const workspacePath = path.join(cwd, 'WORKSPACE.md');
|
|
809
|
-
const contextFiles = [
|
|
810
|
-
...(fs.existsSync(workspacePath) ? [workspacePath] : []),
|
|
811
|
-
...await workspaceLinkedFiles(cwd),
|
|
812
|
-
];
|
|
813
|
-
for (const contextFile of contextFiles) {
|
|
814
|
-
if (fileExtensions.some((extension) => contextFile.endsWith(extension)) && !files.includes(contextFile)) {
|
|
815
|
-
files.push(contextFile);
|
|
816
|
-
}
|
|
817
|
-
}
|
|
818
|
-
}
|
|
756
|
+
const files = scope
|
|
757
|
+
? existingDirs.flatMap(dir => findArtifactFiles(dir, fileExtensions))
|
|
758
|
+
: await collectGraphIndexFiles(cwd, graph);
|
|
819
759
|
const entries = {};
|
|
820
760
|
const tagIndex = {};
|
|
821
761
|
const depGraph = {};
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared artifact source-file enumeration.
|
|
3
|
+
*
|
|
4
|
+
* Index builds and coverage reporting must use the same file set so project
|
|
5
|
+
* context files cannot inflate the indexed count beyond the reported total.
|
|
6
|
+
*
|
|
7
|
+
* @implements jmagly/aiwg#146
|
|
8
|
+
*/
|
|
9
|
+
import fs from 'fs';
|
|
10
|
+
import path from 'path';
|
|
11
|
+
import { DEFAULT_INDEX_EXTENSIONS, GRAPH_CONFIGS, resolveGraphScanDir, } from './types.js';
|
|
12
|
+
import { DEFAULT_PROJECT_AIWG_DIR, resolveProjectAiwgDir, } from '../config/project-artifacts.js';
|
|
13
|
+
import { workspaceLinkedFiles } from '../smiths/context-pipeline/workspace-context.js';
|
|
14
|
+
function pathContains(parent, child) {
|
|
15
|
+
const relative = path.relative(parent, child);
|
|
16
|
+
return relative === '' || (!relative.startsWith('..') && !path.isAbsolute(relative));
|
|
17
|
+
}
|
|
18
|
+
function toPosixPath(value) {
|
|
19
|
+
return value.split(path.sep).join('/');
|
|
20
|
+
}
|
|
21
|
+
export function indexPathFor(cwd, fullPath, graph) {
|
|
22
|
+
if (!graph || graph === 'project') {
|
|
23
|
+
const artifactRoot = resolveProjectAiwgDir(cwd);
|
|
24
|
+
if (pathContains(artifactRoot, fullPath)) {
|
|
25
|
+
const relative = toPosixPath(path.relative(artifactRoot, fullPath));
|
|
26
|
+
return relative ? `${DEFAULT_PROJECT_AIWG_DIR}/${relative}` : DEFAULT_PROJECT_AIWG_DIR;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
const relative = path.relative(cwd, fullPath);
|
|
30
|
+
if (!relative.startsWith('..') && !path.isAbsolute(relative))
|
|
31
|
+
return toPosixPath(relative);
|
|
32
|
+
return fullPath;
|
|
33
|
+
}
|
|
34
|
+
/** Recursively find indexable files, excluding hidden directories such as .index. */
|
|
35
|
+
export function findArtifactFiles(dir, extensions = DEFAULT_INDEX_EXTENSIONS) {
|
|
36
|
+
const results = [];
|
|
37
|
+
if (!fs.existsSync(dir))
|
|
38
|
+
return results;
|
|
39
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
40
|
+
for (const entry of entries) {
|
|
41
|
+
const fullPath = path.join(dir, entry.name);
|
|
42
|
+
if (entry.isSymbolicLink() && !fs.existsSync(fullPath))
|
|
43
|
+
continue;
|
|
44
|
+
if (entry.isDirectory()) {
|
|
45
|
+
if (entry.name.startsWith('.'))
|
|
46
|
+
continue;
|
|
47
|
+
results.push(...findArtifactFiles(fullPath, extensions));
|
|
48
|
+
}
|
|
49
|
+
else if (extensions.some(extension => entry.name.endsWith(extension))) {
|
|
50
|
+
results.push(fullPath);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return results;
|
|
54
|
+
}
|
|
55
|
+
/** Return the exact current source-file set used by a standard graph build. */
|
|
56
|
+
export async function collectGraphIndexFiles(cwd, graph) {
|
|
57
|
+
const config = graph ? GRAPH_CONFIGS[graph] : undefined;
|
|
58
|
+
const scanDirs = config
|
|
59
|
+
? config.scanDirs.map(directory => resolveGraphScanDir(cwd, directory))
|
|
60
|
+
: [resolveProjectAiwgDir(cwd)];
|
|
61
|
+
const extensions = config?.extensions ?? [...DEFAULT_INDEX_EXTENSIONS];
|
|
62
|
+
const files = new Set();
|
|
63
|
+
for (const scanDir of scanDirs) {
|
|
64
|
+
for (const file of findArtifactFiles(scanDir, extensions))
|
|
65
|
+
files.add(file);
|
|
66
|
+
}
|
|
67
|
+
if (!graph || graph === 'project') {
|
|
68
|
+
const workspacePath = path.join(cwd, 'WORKSPACE.md');
|
|
69
|
+
const contextFiles = [
|
|
70
|
+
...(fs.existsSync(workspacePath) ? [workspacePath] : []),
|
|
71
|
+
...await workspaceLinkedFiles(cwd),
|
|
72
|
+
];
|
|
73
|
+
for (const contextFile of contextFiles) {
|
|
74
|
+
if (extensions.some(extension => contextFile.endsWith(extension)))
|
|
75
|
+
files.add(contextFile);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return [...files];
|
|
79
|
+
}
|
|
80
|
+
//# sourceMappingURL=index-files.js.map
|
|
@@ -7,40 +7,23 @@
|
|
|
7
7
|
* @source @src/artifacts/types.ts
|
|
8
8
|
* @tests @test/unit/artifacts/stats.test.ts
|
|
9
9
|
*/
|
|
10
|
-
import
|
|
11
|
-
import path from 'path';
|
|
12
|
-
import { GRAPH_CONFIGS, loadUserGraphConfigs, resolveGraphScanDir } from './types.js';
|
|
10
|
+
import { GRAPH_CONFIGS, loadUserGraphConfigs } from './types.js';
|
|
13
11
|
import { loadIndexStats, loadGraphIndexFile } from './index-reader.js';
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
const
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
const full = path.join(dir, entry.name);
|
|
30
|
-
if (entry.isDirectory()) {
|
|
31
|
-
if (entry.name.startsWith('.'))
|
|
32
|
-
continue; // Skip .index, etc.
|
|
33
|
-
walk(full);
|
|
34
|
-
}
|
|
35
|
-
else if (extensions.some(ext => entry.name.endsWith(ext))) {
|
|
36
|
-
count++;
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
for (const dir of scanDirs) {
|
|
41
|
-
walk(dir);
|
|
42
|
-
}
|
|
43
|
-
return count;
|
|
12
|
+
import { collectGraphIndexFiles, indexPathFor } from './index-files.js';
|
|
13
|
+
/** Calculate coverage over the same current file set used by the index builder. */
|
|
14
|
+
async function calculateCoverage(cwd, stats, graphType) {
|
|
15
|
+
const sourcePaths = new Set((await collectGraphIndexFiles(cwd, graphType))
|
|
16
|
+
.map(file => indexPathFor(cwd, file, graphType)));
|
|
17
|
+
const index = loadGraphIndexFile(cwd, 'metadata.json', graphType);
|
|
18
|
+
const indexed = index
|
|
19
|
+
? Object.keys(index.entries).filter(entryPath => sourcePaths.has(entryPath)).length
|
|
20
|
+
: Math.min(stats.totalArtifacts, sourcePaths.size);
|
|
21
|
+
const totalFiles = sourcePaths.size;
|
|
22
|
+
return {
|
|
23
|
+
indexed,
|
|
24
|
+
totalFiles,
|
|
25
|
+
percentage: totalFiles > 0 ? Math.round((indexed / totalFiles) * 100) : 100,
|
|
26
|
+
};
|
|
44
27
|
}
|
|
45
28
|
/**
|
|
46
29
|
* Show artifact index statistics
|
|
@@ -84,14 +67,10 @@ export async function showStats(cwd, options = {}) {
|
|
|
84
67
|
// JSON mode: aggregate all graphs into one response
|
|
85
68
|
const combined = {};
|
|
86
69
|
for (const { type, stats: s } of availableGraphs) {
|
|
87
|
-
const
|
|
70
|
+
const coverage = await calculateCoverage(cwd, s, type);
|
|
88
71
|
combined[type] = {
|
|
89
72
|
...s,
|
|
90
|
-
coverage
|
|
91
|
-
indexed: s.totalArtifacts,
|
|
92
|
-
totalFiles,
|
|
93
|
-
percentage: totalFiles > 0 ? Math.round((s.totalArtifacts / totalFiles) * 100) : 100,
|
|
94
|
-
},
|
|
73
|
+
coverage,
|
|
95
74
|
};
|
|
96
75
|
}
|
|
97
76
|
console.log(JSON.stringify(combined, null, 2));
|
|
@@ -108,14 +87,10 @@ export async function showStats(cwd, options = {}) {
|
|
|
108
87
|
*/
|
|
109
88
|
async function renderStats(cwd, stats, options, graphType) {
|
|
110
89
|
if (options.json) {
|
|
111
|
-
const
|
|
90
|
+
const coverage = await calculateCoverage(cwd, stats, graphType);
|
|
112
91
|
console.log(JSON.stringify({
|
|
113
92
|
...stats,
|
|
114
|
-
coverage
|
|
115
|
-
indexed: stats.totalArtifacts,
|
|
116
|
-
totalFiles,
|
|
117
|
-
percentage: totalFiles > 0 ? Math.round((stats.totalArtifacts / totalFiles) * 100) : 100,
|
|
118
|
-
},
|
|
93
|
+
coverage,
|
|
119
94
|
}, null, 2));
|
|
120
95
|
return;
|
|
121
96
|
}
|
|
@@ -167,11 +142,8 @@ async function renderStats(cwd, stats, options, graphType) {
|
|
|
167
142
|
}
|
|
168
143
|
console.log('');
|
|
169
144
|
// Coverage
|
|
170
|
-
const
|
|
171
|
-
const coverage = totalFiles > 0
|
|
172
|
-
? Math.round((stats.totalArtifacts / totalFiles) * 100)
|
|
173
|
-
: 100;
|
|
145
|
+
const coverage = await calculateCoverage(cwd, stats, graphType);
|
|
174
146
|
console.log('Index Health:');
|
|
175
|
-
console.log(` Coverage: ${
|
|
147
|
+
console.log(` Coverage: ${coverage.indexed}/${coverage.totalFiles} artifacts indexed (${coverage.percentage}%)`);
|
|
176
148
|
}
|
|
177
149
|
//# sourceMappingURL=stats.js.map
|