aiwg 2026.7.3 → 2026.7.5
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/README.md +7 -1
- package/dist/src/artifacts/browser-export.js +15 -0
- package/dist/src/artifacts/cli.js +90 -4
- package/dist/src/artifacts/fortemi-core-sync.js +7 -0
- package/dist/src/artifacts/index-builder.js +39 -6
- package/dist/src/artifacts/index-status.js +2 -1
- package/dist/src/artifacts/legacy-index-migration.js +169 -0
- package/dist/src/artifacts/query-engine.js +102 -15
- package/dist/src/artifacts/source-graph.js +527 -0
- package/dist/src/artifacts/types.js +112 -2
- package/dist/src/cli/handlers/help.js +2 -0
- package/dist/src/cli/handlers/index.js +3 -1
- package/dist/src/cli/handlers/init.js +9 -6
- package/dist/src/cli/handlers/run.js +2 -2
- package/dist/src/cli/handlers/setup.js +440 -0
- package/dist/src/cli/handlers/use.js +3 -3
- package/dist/src/config/cli.js +3 -3
- package/dist/src/extensions/commands/definitions.js +33 -0
- package/dist/src/mcp/tools/subsystems.mjs +1 -1
- package/docs/cli-reference.md +87 -0
- package/docs/getting-started/existing-project.md +31 -6
- package/docs/integrations/fortemi-index-export.md +32 -0
- package/docs/local-issues.md +6 -0
- package/docs/quickstart.md +12 -1
- package/docs/releases/v2026.7.4-announcement.md +32 -0
- package/docs/releases/v2026.7.5-announcement.md +27 -0
- package/docs/user-level-indices.md +147 -0
- package/package.json +2 -2
- package/prebuilt/fortemi-core/framework/aiwg-fortemi-index-v2.json +1 -1
- package/prebuilt/fortemi-core/framework/manifest.json +2 -2
package/README.md
CHANGED
|
@@ -13,6 +13,11 @@ npm i -g aiwg # install globally
|
|
|
13
13
|
aiwg use sdlc # deploy SDLC framework
|
|
14
14
|
```
|
|
15
15
|
|
|
16
|
+
Then ask your AI assistant to set up the project for AIWG. The agent-led setup
|
|
17
|
+
conversation should establish remotes, issue storage, delivery behavior,
|
|
18
|
+
signing policy, and provider choices; the assistant may call `aiwg setup project`
|
|
19
|
+
as the underlying CLI helper.
|
|
20
|
+
|
|
16
21
|
macOS users: if npm fails with `EACCES` under `/usr/local/lib/node_modules`,
|
|
17
22
|
use the [macOS Install Guide](docs/getting-started/macos-install.md).
|
|
18
23
|
Agents and stewards setting up AIWG end-to-end should use the
|
|
@@ -133,7 +138,8 @@ The user surface is the conversation with your AI tool. You install AIWG, deploy
|
|
|
133
138
|
The CLI exists mostly for the agent to call under the hood. The commands a user typically runs by hand are a short list:
|
|
134
139
|
|
|
135
140
|
- `aiwg use <framework>` — deploy AIWG to your project (one-time per framework, per project)
|
|
136
|
-
-
|
|
141
|
+
- Project setup agent/skill — recommended guided setup conversation for repo, tracker, delivery, signing, and provider policy
|
|
142
|
+
- `aiwg wizard` — guided first-run goal routing
|
|
137
143
|
- `aiwg new <project>` — scaffold a new project
|
|
138
144
|
- `aiwg status` — what's deployed and engaged in this workspace
|
|
139
145
|
- `aiwg doctor` — health check
|
|
@@ -65,6 +65,7 @@ function relationshipsForEntry(entry, graph, recordTypesByPath) {
|
|
|
65
65
|
source_path: edge.path,
|
|
66
66
|
target_path: edge.path,
|
|
67
67
|
direction: prefix,
|
|
68
|
+
metadata: Object.fromEntries(Object.entries(edge).filter(([key]) => key !== "path" && key !== "type")),
|
|
68
69
|
});
|
|
69
70
|
};
|
|
70
71
|
for (const edge of edges.upstream)
|
|
@@ -125,6 +126,20 @@ function recordTypeForEntry(entry, schemaVersion) {
|
|
|
125
126
|
return "aiwg.memory.entry";
|
|
126
127
|
case "issue":
|
|
127
128
|
return "aiwg.issue";
|
|
129
|
+
case "source.file":
|
|
130
|
+
return "aiwg.source.file";
|
|
131
|
+
case "source.module":
|
|
132
|
+
return "aiwg.source.module";
|
|
133
|
+
case "source.package":
|
|
134
|
+
return "aiwg.source.package";
|
|
135
|
+
case "source.builtin":
|
|
136
|
+
return "aiwg.source.builtin";
|
|
137
|
+
case "source.asset":
|
|
138
|
+
return "aiwg.source.asset";
|
|
139
|
+
case "source.unresolved":
|
|
140
|
+
return "aiwg.source.unresolved";
|
|
141
|
+
case "source.entrypoint":
|
|
142
|
+
return "aiwg.source.entrypoint";
|
|
128
143
|
default:
|
|
129
144
|
if (pathText.includes("/research/references/") ||
|
|
130
145
|
/^ref[-_]/i.test(entry.name ?? entry.title))
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
* @source @src/cli/handlers/subcommands.ts
|
|
17
17
|
* @tests @test/unit/artifacts/cli.test.ts
|
|
18
18
|
*/
|
|
19
|
-
import { GRAPH_CONFIGS, loadUserGraphConfigs } from './types.js';
|
|
19
|
+
import { GRAPH_CONFIGS, loadUserGraphConfigs, loadGlobalGraphConfigs } from './types.js';
|
|
20
20
|
import { SUPPORTED_VIEWS } from './corpus-views/renderers.js';
|
|
21
21
|
/** Parse --graph flag from args, returns undefined for "all graphs" */
|
|
22
22
|
function parseGraphFlag(args) {
|
|
@@ -30,6 +30,7 @@ function parseGraphFlag(args) {
|
|
|
30
30
|
const val = args[idx + 1];
|
|
31
31
|
// Load user-defined graphs so validation is complete
|
|
32
32
|
loadUserGraphConfigs(process.cwd());
|
|
33
|
+
loadGlobalGraphConfigs();
|
|
33
34
|
if (val in GRAPH_CONFIGS)
|
|
34
35
|
return val;
|
|
35
36
|
// Corpus markdown views (#1490) are valid --graph targets for `index build`.
|
|
@@ -135,6 +136,9 @@ export async function main(args) {
|
|
|
135
136
|
case 'sync':
|
|
136
137
|
await handleSync(subcommandArgs);
|
|
137
138
|
break;
|
|
139
|
+
case 'migrate-legacy':
|
|
140
|
+
await handleMigrateLegacy(subcommandArgs);
|
|
141
|
+
break;
|
|
138
142
|
case 'deps':
|
|
139
143
|
await handleDeps(subcommandArgs);
|
|
140
144
|
break;
|
|
@@ -186,7 +190,7 @@ export async function main(args) {
|
|
|
186
190
|
break;
|
|
187
191
|
default:
|
|
188
192
|
console.error(`Error: Unknown index subcommand '${subcommand}'`);
|
|
189
|
-
console.log('Available: build, query, discover, show, export, sync, deps, stats, status, list, neighbors, set, embed, similar, dedup-report, watch');
|
|
193
|
+
console.log('Available: build, query, discover, show, export, sync, migrate-legacy, deps, stats, status, list, neighbors, set, embed, similar, dedup-report, watch');
|
|
190
194
|
process.exit(1);
|
|
191
195
|
}
|
|
192
196
|
}
|
|
@@ -200,6 +204,7 @@ function printIndexUsage() {
|
|
|
200
204
|
console.log(' show Print the full text of a specific skill/agent/command/rule');
|
|
201
205
|
console.log(' export Export a browser-consumable index contract');
|
|
202
206
|
console.log(' sync Materialize the Fortemi Core static index cache');
|
|
207
|
+
console.log(' migrate-legacy Move legacy root indexes into graph sidecar indexes');
|
|
203
208
|
console.log(' deps Show artifact dependency graph');
|
|
204
209
|
console.log(' stats Show index statistics');
|
|
205
210
|
console.log(' status Enumerate the index-graph registry (freshness + drift); alias: list');
|
|
@@ -211,7 +216,7 @@ function printIndexUsage() {
|
|
|
211
216
|
console.log(' watch Start a filesystem watcher for automatic incremental index updates');
|
|
212
217
|
console.log('');
|
|
213
218
|
console.log('Options:');
|
|
214
|
-
console.log(' --graph <name> Target a specific graph (framework, project, codebase, or user-defined)');
|
|
219
|
+
console.log(' --graph <name> Target a specific graph (framework, project, codebase, source, user, or user-defined)');
|
|
215
220
|
console.log(' --all Build all known graphs (including user-defined)');
|
|
216
221
|
console.log('');
|
|
217
222
|
console.log('Examples:');
|
|
@@ -225,6 +230,7 @@ function printIndexUsage() {
|
|
|
225
230
|
console.log(' aiwg index show skill flow-deploy-to-production --json');
|
|
226
231
|
console.log(' aiwg index show agent aiwg-steward');
|
|
227
232
|
console.log(' aiwg index export --format fortemi --graph project --out aiwg-fortemi-index.json');
|
|
233
|
+
console.log(' aiwg index migrate-legacy --scope project --dry-run');
|
|
228
234
|
console.log(' aiwg index query "authentication" --type use-case');
|
|
229
235
|
console.log(' aiwg index query "security rules" --graph framework --json');
|
|
230
236
|
console.log(' aiwg index query "mixture of experts" --fulltext --graph papers # body text, BM25');
|
|
@@ -332,7 +338,7 @@ async function handleBuild(args) {
|
|
|
332
338
|
console.log(' --scope <dir> Limit scan to a specific subdirectory');
|
|
333
339
|
console.log(' --graph <name> Build a specific graph only (built-in or user-defined)');
|
|
334
340
|
console.log('');
|
|
335
|
-
console.log('Built-in graph names: project, codebase, framework');
|
|
341
|
+
console.log('Built-in graph names: project, codebase, source, user, framework');
|
|
336
342
|
console.log('User-defined graphs: configure under index.graphs in .aiwg/aiwg.config');
|
|
337
343
|
console.log('');
|
|
338
344
|
console.log('Default behavior (no --graph): builds all graphs with defaultBuild: true');
|
|
@@ -342,6 +348,8 @@ async function handleBuild(args) {
|
|
|
342
348
|
console.log(' aiwg index build');
|
|
343
349
|
console.log(' aiwg index build --force');
|
|
344
350
|
console.log(' aiwg index build --graph codebase --force');
|
|
351
|
+
console.log(' aiwg index build --graph source --force');
|
|
352
|
+
console.log(' aiwg index build --graph user --force');
|
|
345
353
|
console.log(' aiwg index build --graph references # user-defined graph');
|
|
346
354
|
console.log(' aiwg index build --scope documentation/references');
|
|
347
355
|
console.log(' aiwg index build --all');
|
|
@@ -363,6 +371,7 @@ async function handleBuild(args) {
|
|
|
363
371
|
}
|
|
364
372
|
// Load user-defined graphs
|
|
365
373
|
loadUserGraphConfigs(cwd);
|
|
374
|
+
loadGlobalGraphConfigs();
|
|
366
375
|
let jsonBuilt = false;
|
|
367
376
|
if (graph) {
|
|
368
377
|
// --graph X: build the JSON graph if X is one; otherwise X may be a
|
|
@@ -821,6 +830,83 @@ async function handleSync(args) {
|
|
|
821
830
|
process.exit(1);
|
|
822
831
|
}
|
|
823
832
|
}
|
|
833
|
+
/**
|
|
834
|
+
* Handle 'index migrate-legacy' command.
|
|
835
|
+
*/
|
|
836
|
+
async function handleMigrateLegacy(args) {
|
|
837
|
+
if (args.includes('--help') || args.includes('-h')) {
|
|
838
|
+
console.log('Usage: aiwg index migrate-legacy [--scope project|user|global | --all] [options]');
|
|
839
|
+
console.log('');
|
|
840
|
+
console.log('Migrates compatible legacy root index files into graph sidecar index');
|
|
841
|
+
console.log('directories. Project scope moves .aiwg/.index/*.json to');
|
|
842
|
+
console.log('.aiwg/.index/project/*.json and refreshes the Fortemi Core static');
|
|
843
|
+
console.log('cache. User/global scopes report or migrate their corresponding');
|
|
844
|
+
console.log('sidecar locations without modifying packaged/prebuilt AIWG indexes.');
|
|
845
|
+
console.log('');
|
|
846
|
+
console.log('Options:');
|
|
847
|
+
console.log(' --scope <name> Scope to migrate: project, user, or global (default: project)');
|
|
848
|
+
console.log(' --all Migrate project, user, and global scopes');
|
|
849
|
+
console.log(' --dry-run Print planned changes without writing files');
|
|
850
|
+
console.log(' --no-fortemi-sync Do not refresh the project Fortemi Core static cache');
|
|
851
|
+
console.log(' --generated-at <iso> Override generated timestamp for deterministic fixtures');
|
|
852
|
+
console.log(' --json Print the migration report as JSON');
|
|
853
|
+
console.log('');
|
|
854
|
+
console.log('Examples:');
|
|
855
|
+
console.log(' aiwg index migrate-legacy --scope project --dry-run');
|
|
856
|
+
console.log(' aiwg index migrate-legacy --all --json');
|
|
857
|
+
return;
|
|
858
|
+
}
|
|
859
|
+
const all = args.includes('--all');
|
|
860
|
+
const scopeValue = parseFlagValue(args, '--scope', 'Error: --scope requires project, user, or global');
|
|
861
|
+
if (all && scopeValue) {
|
|
862
|
+
console.error('Error: pass either --all or --scope, not both');
|
|
863
|
+
process.exit(1);
|
|
864
|
+
}
|
|
865
|
+
const allowedScopes = ['project', 'user', 'global'];
|
|
866
|
+
const scopes = all
|
|
867
|
+
? [...allowedScopes]
|
|
868
|
+
: scopeValue
|
|
869
|
+
? [scopeValue]
|
|
870
|
+
: ['project'];
|
|
871
|
+
const invalidScope = scopes.find((scope) => !allowedScopes.includes(scope));
|
|
872
|
+
if (invalidScope) {
|
|
873
|
+
console.error('Error: --scope must be project, user, or global');
|
|
874
|
+
process.exit(1);
|
|
875
|
+
}
|
|
876
|
+
const generatedAt = parseFlagValue(args, '--generated-at', 'Error: --generated-at requires an ISO timestamp value');
|
|
877
|
+
const { migrateLegacyIndex } = await import('./legacy-index-migration.js');
|
|
878
|
+
try {
|
|
879
|
+
const report = migrateLegacyIndex(process.cwd(), {
|
|
880
|
+
scopes: scopes,
|
|
881
|
+
dryRun: args.includes('--dry-run'),
|
|
882
|
+
syncFortemi: !args.includes('--no-fortemi-sync'),
|
|
883
|
+
generatedAt,
|
|
884
|
+
});
|
|
885
|
+
if (args.includes('--json')) {
|
|
886
|
+
console.log(JSON.stringify(report, null, 2));
|
|
887
|
+
return;
|
|
888
|
+
}
|
|
889
|
+
console.log(`Legacy index migration ${report.dryRun ? '(DRY RUN)' : 'complete'}`);
|
|
890
|
+
for (const result of report.results) {
|
|
891
|
+
const entries = result.entries === null ? 'unknown' : String(result.entries);
|
|
892
|
+
const detail = result.reason ? ` — ${result.reason}` : '';
|
|
893
|
+
console.log(` ${result.scope}: ${result.status} (${entries} entries)${detail}`);
|
|
894
|
+
for (const file of result.files) {
|
|
895
|
+
console.log(` ${file.name}: ${file.status}`);
|
|
896
|
+
}
|
|
897
|
+
if (result.fortemiCore) {
|
|
898
|
+
console.log(` fortemi-core: ${result.fortemiCore.status} (${result.fortemiCore.itemCount} item(s)) → ${result.fortemiCore.exportPath}`);
|
|
899
|
+
}
|
|
900
|
+
}
|
|
901
|
+
if (report.reportPath) {
|
|
902
|
+
console.log(` report: ${report.reportPath}`);
|
|
903
|
+
}
|
|
904
|
+
}
|
|
905
|
+
catch (err) {
|
|
906
|
+
console.error('Error: ' + (err instanceof Error ? err.message : String(err)));
|
|
907
|
+
process.exit(1);
|
|
908
|
+
}
|
|
909
|
+
}
|
|
824
910
|
/**
|
|
825
911
|
* Handle 'index deps' command
|
|
826
912
|
*
|
|
@@ -1,9 +1,16 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { createHash } from "node:crypto";
|
|
4
|
+
import { GRAPH_CONFIGS, loadGlobalGraphConfigs } from "./types.js";
|
|
4
5
|
import { buildAiwgFortemiIndexExport, } from "./browser-export.js";
|
|
5
6
|
import { loadGraphIndexFile } from "./index-reader.js";
|
|
6
7
|
function syncDir(cwd, graph) {
|
|
8
|
+
loadGlobalGraphConfigs();
|
|
9
|
+
const config = GRAPH_CONFIGS[graph];
|
|
10
|
+
if (graph === "framework" || config?.shared) {
|
|
11
|
+
const xdgData = process.env.XDG_DATA_HOME ?? path.join(process.env.HOME ?? cwd, ".local", "share");
|
|
12
|
+
return path.join(xdgData, "aiwg", "index", "fortemi-core", graph);
|
|
13
|
+
}
|
|
7
14
|
return path.join(cwd, ".aiwg", ".index", "fortemi-core", graph);
|
|
8
15
|
}
|
|
9
16
|
function findPackageRoot(startDir) {
|
|
@@ -13,10 +13,29 @@ import fs from 'fs';
|
|
|
13
13
|
import path from 'path';
|
|
14
14
|
import { createHash } from 'crypto';
|
|
15
15
|
import { load as loadYaml } from 'js-yaml';
|
|
16
|
-
import { INDEX_VERSION, INDEX_DIR, PHASE_DIRECTORIES, GRAPH_CONFIGS, loadUserGraphConfigs } from './types.js';
|
|
16
|
+
import { INDEX_VERSION, INDEX_DIR, PHASE_DIRECTORIES, GRAPH_CONFIGS, loadUserGraphConfigs, loadGlobalGraphConfigs } from './types.js';
|
|
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
|
+
function expandScanDir(cwd, scanDir) {
|
|
21
|
+
if (scanDir === '~')
|
|
22
|
+
return process.env.HOME ?? scanDir;
|
|
23
|
+
if (scanDir.startsWith('~/')) {
|
|
24
|
+
return path.join(process.env.HOME ?? '', scanDir.slice(2));
|
|
25
|
+
}
|
|
26
|
+
if (path.isAbsolute(scanDir))
|
|
27
|
+
return scanDir;
|
|
28
|
+
return path.join(cwd, scanDir);
|
|
29
|
+
}
|
|
30
|
+
function indexPathFor(cwd, fullPath) {
|
|
31
|
+
const rel = path.relative(cwd, fullPath);
|
|
32
|
+
if (!rel.startsWith('..') && !path.isAbsolute(rel))
|
|
33
|
+
return rel;
|
|
34
|
+
return fullPath;
|
|
35
|
+
}
|
|
36
|
+
function absoluteEntryPath(cwd, entryPath) {
|
|
37
|
+
return path.isAbsolute(entryPath) ? entryPath : path.join(cwd, entryPath);
|
|
38
|
+
}
|
|
20
39
|
/**
|
|
21
40
|
* Parse YAML frontmatter from markdown content
|
|
22
41
|
*/
|
|
@@ -498,6 +517,7 @@ export async function buildIndex(cwd, options = {}) {
|
|
|
498
517
|
const startTime = Date.now();
|
|
499
518
|
// Ensure user-defined graphs are loaded
|
|
500
519
|
loadUserGraphConfigs(cwd);
|
|
520
|
+
loadGlobalGraphConfigs();
|
|
501
521
|
// Determine scan directories based on graph type
|
|
502
522
|
const graphConfig = graph ? GRAPH_CONFIGS[graph] : undefined;
|
|
503
523
|
let scanDirs;
|
|
@@ -508,7 +528,7 @@ export async function buildIndex(cwd, options = {}) {
|
|
|
508
528
|
fileExtensions = ['.md', '.yaml', '.json'];
|
|
509
529
|
}
|
|
510
530
|
else if (graphConfig) {
|
|
511
|
-
scanDirs = graphConfig.scanDirs.map(d =>
|
|
531
|
+
scanDirs = graphConfig.scanDirs.map(d => expandScanDir(cwd, d));
|
|
512
532
|
fileExtensions = graphConfig.extensions;
|
|
513
533
|
}
|
|
514
534
|
else {
|
|
@@ -548,6 +568,19 @@ export async function buildIndex(cwd, options = {}) {
|
|
|
548
568
|
fs.mkdirSync(indexOutputDir, { recursive: true });
|
|
549
569
|
// effectiveOutputCwd is used for backward-compat loadMetadataIndex calls
|
|
550
570
|
const effectiveOutputCwd = outputDir ?? cwd;
|
|
571
|
+
if (graph === 'source') {
|
|
572
|
+
const { buildSourceGraphIndex } = await import('./source-graph.js');
|
|
573
|
+
await buildSourceGraphIndex({
|
|
574
|
+
cwd,
|
|
575
|
+
outputDir: indexOutputDir,
|
|
576
|
+
effectiveOutputCwd,
|
|
577
|
+
verbose,
|
|
578
|
+
});
|
|
579
|
+
const buildTimeMs = Date.now() - startTime;
|
|
580
|
+
console.log(`Source graph built in ${buildTimeMs}ms`);
|
|
581
|
+
console.log(` Output: ${INDEX_DIR}/source/`);
|
|
582
|
+
return;
|
|
583
|
+
}
|
|
551
584
|
// Load existing index for incremental updates
|
|
552
585
|
const existingIndex = force ? null : loadGraphIndexFile(effectiveOutputCwd, 'metadata.json', graph);
|
|
553
586
|
const existingEntries = existingIndex?.entries ?? {};
|
|
@@ -577,7 +610,7 @@ export async function buildIndex(cwd, options = {}) {
|
|
|
577
610
|
let unchangedCount = 0;
|
|
578
611
|
const useFilenameMetadata = graphConfig?.nodeStrategy === 'filename-metadata';
|
|
579
612
|
for (const fullPath of files) {
|
|
580
|
-
const relativePath =
|
|
613
|
+
const relativePath = indexPathFor(cwd, fullPath);
|
|
581
614
|
let entry;
|
|
582
615
|
if (useFilenameMetadata) {
|
|
583
616
|
// Filename-metadata strategy: derive metadata from filename, skip content read.
|
|
@@ -728,7 +761,7 @@ export async function buildIndex(cwd, options = {}) {
|
|
|
728
761
|
// Build REF-XXX → path map from all entries with ref frontmatter
|
|
729
762
|
const entryFrontmatter = new Map();
|
|
730
763
|
for (const entryPath of Object.keys(entries)) {
|
|
731
|
-
const fullPath =
|
|
764
|
+
const fullPath = absoluteEntryPath(cwd, entryPath);
|
|
732
765
|
if (fs.existsSync(fullPath)) {
|
|
733
766
|
const content = fs.readFileSync(fullPath, 'utf-8');
|
|
734
767
|
const { data } = parseFrontmatter(content);
|
|
@@ -739,7 +772,7 @@ export async function buildIndex(cwd, options = {}) {
|
|
|
739
772
|
// Parse each entry as a citation sidecar and extract edges
|
|
740
773
|
let citationEdgeCount = 0;
|
|
741
774
|
for (const entryPath of Object.keys(entries)) {
|
|
742
|
-
const fullPath =
|
|
775
|
+
const fullPath = absoluteEntryPath(cwd, entryPath);
|
|
743
776
|
if (!fs.existsSync(fullPath))
|
|
744
777
|
continue;
|
|
745
778
|
const content = fs.readFileSync(fullPath, 'utf-8');
|
|
@@ -848,7 +881,7 @@ export async function buildIndex(cwd, options = {}) {
|
|
|
848
881
|
console.log(` Pruned ${manifestStats.pruned} stale manifest entries (files no longer on disk)`);
|
|
849
882
|
}
|
|
850
883
|
}
|
|
851
|
-
const displayDir = graph ?
|
|
884
|
+
const displayDir = graph ? indexOutputDir : `${INDEX_DIR}/`;
|
|
852
885
|
console.log(` Output: ${displayDir}`);
|
|
853
886
|
}
|
|
854
887
|
//# sourceMappingURL=index-builder.js.map
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
*/
|
|
19
19
|
import * as fs from 'node:fs';
|
|
20
20
|
import * as path from 'node:path';
|
|
21
|
-
import { GRAPH_CONFIGS, BUILTIN_GRAPH_CONFIGS, getGraphIndexDir, loadUserGraphConfigs, } from './types.js';
|
|
21
|
+
import { GRAPH_CONFIGS, BUILTIN_GRAPH_CONFIGS, getGraphIndexDir, loadGlobalGraphConfigs, loadUserGraphConfigs, } from './types.js';
|
|
22
22
|
import { getFortemiCoreSyncStatus, } from './fortemi-core-sync.js';
|
|
23
23
|
function readBuiltMeta(indexDir) {
|
|
24
24
|
try {
|
|
@@ -47,6 +47,7 @@ export function collectIndexStatus(cwd, nowMs) {
|
|
|
47
47
|
// instead of letting malformed defs vanish (#1624).
|
|
48
48
|
const warnings = [];
|
|
49
49
|
loadUserGraphConfigs(cwd, warnings);
|
|
50
|
+
loadGlobalGraphConfigs(warnings);
|
|
50
51
|
const now = nowMs ?? Date.now();
|
|
51
52
|
const graphs = [];
|
|
52
53
|
for (const [name, config] of Object.entries(GRAPH_CONFIGS)) {
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import os from 'node:os';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { createHash } from 'node:crypto';
|
|
5
|
+
import { INDEX_VERSION, getGraphIndexDir } from './types.js';
|
|
6
|
+
import { syncFortemiCoreIndex } from './fortemi-core-sync.js';
|
|
7
|
+
const INDEX_FILES = ['metadata.json', 'tags.json', 'dependencies.json', 'stats.json'];
|
|
8
|
+
function sha256File(filePath) {
|
|
9
|
+
if (!fs.existsSync(filePath))
|
|
10
|
+
return null;
|
|
11
|
+
return createHash('sha256').update(fs.readFileSync(filePath)).digest('hex');
|
|
12
|
+
}
|
|
13
|
+
function readJson(filePath) {
|
|
14
|
+
try {
|
|
15
|
+
return { value: JSON.parse(fs.readFileSync(filePath, 'utf-8')), reason: null };
|
|
16
|
+
}
|
|
17
|
+
catch (err) {
|
|
18
|
+
return {
|
|
19
|
+
value: null,
|
|
20
|
+
reason: err instanceof Error ? err.message : String(err),
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
function resolveHome(options) {
|
|
25
|
+
return options.homeDir ?? process.env.AIWG_HOME ?? os.homedir();
|
|
26
|
+
}
|
|
27
|
+
function resolveXdgData(options) {
|
|
28
|
+
return options.xdgDataHome ?? process.env.XDG_DATA_HOME ?? path.join(resolveHome(options), '.local', 'share');
|
|
29
|
+
}
|
|
30
|
+
function scopePaths(cwd, scope, options) {
|
|
31
|
+
switch (scope) {
|
|
32
|
+
case 'project':
|
|
33
|
+
return {
|
|
34
|
+
sourceDir: path.join(cwd, '.aiwg', '.index'),
|
|
35
|
+
destinationDir: getGraphIndexDir(cwd, 'project'),
|
|
36
|
+
syncRoot: cwd,
|
|
37
|
+
graph: 'project',
|
|
38
|
+
};
|
|
39
|
+
case 'user': {
|
|
40
|
+
const home = resolveHome(options);
|
|
41
|
+
return {
|
|
42
|
+
sourceDir: path.join(home, '.aiwg', '.index'),
|
|
43
|
+
destinationDir: path.join(home, '.aiwg', '.index', 'user'),
|
|
44
|
+
syncRoot: null,
|
|
45
|
+
graph: 'user',
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
case 'global': {
|
|
49
|
+
const xdg = resolveXdgData(options);
|
|
50
|
+
return {
|
|
51
|
+
sourceDir: path.join(xdg, 'aiwg', 'index'),
|
|
52
|
+
destinationDir: path.join(xdg, 'aiwg', 'index', 'global'),
|
|
53
|
+
syncRoot: null,
|
|
54
|
+
graph: 'global',
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
function validateLegacyMetadata(sourceDir) {
|
|
60
|
+
const metadataPath = path.join(sourceDir, 'metadata.json');
|
|
61
|
+
if (!fs.existsSync(metadataPath)) {
|
|
62
|
+
return { index: null, reason: 'metadata.json not found' };
|
|
63
|
+
}
|
|
64
|
+
const parsed = readJson(metadataPath);
|
|
65
|
+
if (!parsed.value) {
|
|
66
|
+
return { index: null, reason: `metadata.json is unreadable: ${parsed.reason}` };
|
|
67
|
+
}
|
|
68
|
+
if (parsed.value.version !== INDEX_VERSION) {
|
|
69
|
+
return {
|
|
70
|
+
index: null,
|
|
71
|
+
reason: `metadata.json schema version ${String(parsed.value.version ?? 'missing')} is not compatible with ${INDEX_VERSION}`,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
if (!parsed.value.entries || typeof parsed.value.entries !== 'object' || Array.isArray(parsed.value.entries)) {
|
|
75
|
+
return { index: null, reason: 'metadata.json entries must be an object' };
|
|
76
|
+
}
|
|
77
|
+
return { index: parsed.value, reason: null };
|
|
78
|
+
}
|
|
79
|
+
function migrateOneScope(cwd, scope, options) {
|
|
80
|
+
const { sourceDir, destinationDir, syncRoot, graph } = scopePaths(cwd, scope, options);
|
|
81
|
+
const baseResult = {
|
|
82
|
+
scope,
|
|
83
|
+
sourceDir,
|
|
84
|
+
destinationDir,
|
|
85
|
+
status: 'skipped',
|
|
86
|
+
files: [],
|
|
87
|
+
entries: null,
|
|
88
|
+
reason: null,
|
|
89
|
+
fortemiCore: null,
|
|
90
|
+
};
|
|
91
|
+
if (!fs.existsSync(sourceDir)) {
|
|
92
|
+
return { ...baseResult, reason: 'legacy index directory not found' };
|
|
93
|
+
}
|
|
94
|
+
if (path.resolve(sourceDir) === path.resolve(destinationDir)) {
|
|
95
|
+
return {
|
|
96
|
+
...baseResult,
|
|
97
|
+
reason: 'source and destination are the same directory; no migration needed',
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
const validation = validateLegacyMetadata(sourceDir);
|
|
101
|
+
if (!validation.index) {
|
|
102
|
+
return {
|
|
103
|
+
...baseResult,
|
|
104
|
+
status: 'needs-rebuild',
|
|
105
|
+
reason: validation.reason,
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
let changed = false;
|
|
109
|
+
let anyCreated = false;
|
|
110
|
+
for (const name of INDEX_FILES) {
|
|
111
|
+
const sourcePath = path.join(sourceDir, name);
|
|
112
|
+
const destinationPath = path.join(destinationDir, name);
|
|
113
|
+
if (!fs.existsSync(sourcePath)) {
|
|
114
|
+
baseResult.files.push({ name, status: 'skipped' });
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
const sourceHash = sha256File(sourcePath);
|
|
118
|
+
const destinationHash = sha256File(destinationPath);
|
|
119
|
+
const exists = destinationHash !== null;
|
|
120
|
+
const same = sourceHash !== null && sourceHash === destinationHash;
|
|
121
|
+
const status = same ? 'unchanged' : exists ? 'updated' : 'created';
|
|
122
|
+
baseResult.files.push({ name, status });
|
|
123
|
+
if (!same) {
|
|
124
|
+
changed = true;
|
|
125
|
+
if (!exists)
|
|
126
|
+
anyCreated = true;
|
|
127
|
+
if (!options.dryRun) {
|
|
128
|
+
fs.mkdirSync(destinationDir, { recursive: true });
|
|
129
|
+
fs.copyFileSync(sourcePath, destinationPath);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
baseResult.entries = Object.keys(validation.index.entries).length;
|
|
134
|
+
baseResult.status = changed ? (anyCreated ? 'created' : 'updated') : 'unchanged';
|
|
135
|
+
if (!options.dryRun && options.syncFortemi !== false && syncRoot) {
|
|
136
|
+
const manifest = syncFortemiCoreIndex(syncRoot, {
|
|
137
|
+
graph,
|
|
138
|
+
generatedAt: options.generatedAt,
|
|
139
|
+
});
|
|
140
|
+
baseResult.fortemiCore = {
|
|
141
|
+
status: manifest.status,
|
|
142
|
+
exportPath: manifest.export_path,
|
|
143
|
+
itemCount: manifest.item_count,
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
return baseResult;
|
|
147
|
+
}
|
|
148
|
+
function defaultReportPath(cwd) {
|
|
149
|
+
return path.join(cwd, '.aiwg', '.index', 'migrations', 'legacy-index-migration.json');
|
|
150
|
+
}
|
|
151
|
+
export function migrateLegacyIndex(cwd, options = {}) {
|
|
152
|
+
const generatedAt = options.generatedAt ?? new Date().toISOString();
|
|
153
|
+
const scopes = options.scopes?.length ? options.scopes : ['project'];
|
|
154
|
+
const results = scopes.map((scope) => migrateOneScope(cwd, scope, { ...options, generatedAt }));
|
|
155
|
+
const report = {
|
|
156
|
+
schemaVersion: 'aiwg.legacy-index-migration.v1',
|
|
157
|
+
dryRun: options.dryRun === true,
|
|
158
|
+
generatedAt,
|
|
159
|
+
results,
|
|
160
|
+
reportPath: options.dryRun ? null : defaultReportPath(cwd),
|
|
161
|
+
};
|
|
162
|
+
if (!options.dryRun) {
|
|
163
|
+
const reportPath = defaultReportPath(cwd);
|
|
164
|
+
fs.mkdirSync(path.dirname(reportPath), { recursive: true });
|
|
165
|
+
fs.writeFileSync(reportPath, JSON.stringify(report, null, 2) + '\n', 'utf-8');
|
|
166
|
+
}
|
|
167
|
+
return report;
|
|
168
|
+
}
|
|
169
|
+
//# sourceMappingURL=legacy-index-migration.js.map
|