aiwg 2026.7.4 → 2026.7.7

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.
Files changed (32) hide show
  1. package/README.md +13 -2
  2. package/dist/src/artifacts/browser-export.js +17 -2
  3. package/dist/src/artifacts/cli.js +130 -13
  4. package/dist/src/artifacts/fortemi-core-query-adapter.js +1 -1
  5. package/dist/src/artifacts/fortemi-core-sync.js +7 -0
  6. package/dist/src/artifacts/index-builder.js +39 -6
  7. package/dist/src/artifacts/index-status.js +2 -1
  8. package/dist/src/artifacts/legacy-index-migration.js +169 -0
  9. package/dist/src/artifacts/query-engine.js +363 -141
  10. package/dist/src/artifacts/source-graph.js +527 -0
  11. package/dist/src/artifacts/types.js +119 -7
  12. package/dist/src/cli/handlers/help.js +2 -0
  13. package/dist/src/cli/handlers/index.js +3 -1
  14. package/dist/src/cli/handlers/init.js +9 -6
  15. package/dist/src/cli/handlers/run.js +2 -2
  16. package/dist/src/cli/handlers/setup.js +440 -0
  17. package/dist/src/cli/handlers/use.js +3 -3
  18. package/dist/src/config/cli.js +3 -3
  19. package/dist/src/extensions/commands/definitions.js +33 -0
  20. package/dist/src/mcp/tools/subsystems.mjs +1 -1
  21. package/docs/cli-reference.md +117 -25
  22. package/docs/getting-started/existing-project.md +31 -6
  23. package/docs/integrations/fortemi-index-export.md +32 -0
  24. package/docs/local-issues.md +6 -0
  25. package/docs/quickstart.md +12 -1
  26. package/docs/releases/v2026.7.5-announcement.md +27 -0
  27. package/docs/releases/v2026.7.6-announcement.md +34 -0
  28. package/docs/releases/v2026.7.7-announcement.md +32 -0
  29. package/docs/user-level-indices.md +147 -0
  30. package/package.json +2 -2
  31. package/prebuilt/fortemi-core/framework/aiwg-fortemi-index-v2.json +1 -1
  32. 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
- - `aiwg wizard` — guided first-run setup
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
@@ -1089,13 +1095,18 @@ The headline operator surface for finding and reading AIWG capabilities. Most AI
1089
1095
  aiwg discover "deploy production" # → flow-deploy-to-production
1090
1096
  aiwg discover "create intake" # → intake-* family
1091
1097
  aiwg discover "audit security" --type skill --limit 5
1092
- aiwg discover "<phrase>" --json # stable schema for sub-agents
1098
+ aiwg discover "<phrase>" --format json # stable ids for sub-agents, no paths
1099
+ aiwg discover "<phrase>" --format json --compact
1093
1100
 
1094
1101
  # Fetch the full body of a specific artifact (companion to discover)
1102
+ aiwg show skill aiwg:skill:6f1477d99813ca8d
1095
1103
  aiwg show skill flow-deploy-to-production
1096
1104
  aiwg show agent aiwg-steward
1097
1105
  aiwg show command discover
1098
1106
  aiwg show rule no-attribution
1107
+
1108
+ # Inspect Fortemi metadata and resolved paths when needed
1109
+ aiwg show metadata aiwg:skill:6f1477d99813ca8d --json
1099
1110
  ```
1100
1111
 
1101
1112
  The kernel quickrefs ship **curated, validated discovery phrases per capability domain** — phrases tested against the live scorer to surface the right top-3 candidates. The 6 self-maintenance ops (`steward`, `aiwg-doctor`, `aiwg-refresh`, `aiwg-status`, `aiwg-help`, `use`) stay loaded so the agent retains repair surfaces even when discovery itself is broken. See [`docs/discovery-and-kernel-skills.md`](docs/discovery-and-kernel-skills.md) for the full best-practices guide, ASCII flow diagrams, and verification steps.
@@ -6,7 +6,7 @@ function stableArtifactId(artifactPath) {
6
6
  return ("aiwg:artifact:" +
7
7
  createHash("sha256").update(artifactPath).digest("hex").slice(0, 16));
8
8
  }
9
- function stableRecordId(recordType, artifactPath) {
9
+ export function stableRecordId(recordType, artifactPath) {
10
10
  if (recordType === "aiwg.artifact")
11
11
  return stableArtifactId(artifactPath);
12
12
  return (recordType.replaceAll(".", ":") +
@@ -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)
@@ -78,7 +79,7 @@ function relationshipsForEntry(entry, graph, recordTypesByPath) {
78
79
  return left.target_id.localeCompare(right.target_id);
79
80
  });
80
81
  }
81
- function recordTypeForEntry(entry, schemaVersion) {
82
+ export function recordTypeForEntry(entry, schemaVersion) {
82
83
  if (schemaVersion === "v1")
83
84
  return "aiwg.artifact";
84
85
  const normalized = entry.type.toLowerCase().replace(/_/g, "-");
@@ -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:');
@@ -223,8 +228,10 @@ function printIndexUsage() {
223
228
  console.log(' aiwg index discover "audit security" --type skill');
224
229
  console.log(' aiwg index show skill intake-wizard');
225
230
  console.log(' aiwg index show skill flow-deploy-to-production --json');
231
+ console.log(' aiwg index show metadata aiwg:skill:4840fa441622f676 --json');
226
232
  console.log(' aiwg index show agent aiwg-steward');
227
233
  console.log(' aiwg index export --format fortemi --graph project --out aiwg-fortemi-index.json');
234
+ console.log(' aiwg index migrate-legacy --scope project --dry-run');
228
235
  console.log(' aiwg index query "authentication" --type use-case');
229
236
  console.log(' aiwg index query "security rules" --graph framework --json');
230
237
  console.log(' aiwg index query "mixture of experts" --fulltext --graph papers # body text, BM25');
@@ -332,7 +339,7 @@ async function handleBuild(args) {
332
339
  console.log(' --scope <dir> Limit scan to a specific subdirectory');
333
340
  console.log(' --graph <name> Build a specific graph only (built-in or user-defined)');
334
341
  console.log('');
335
- console.log('Built-in graph names: project, codebase, framework');
342
+ console.log('Built-in graph names: project, codebase, source, user, framework');
336
343
  console.log('User-defined graphs: configure under index.graphs in .aiwg/aiwg.config');
337
344
  console.log('');
338
345
  console.log('Default behavior (no --graph): builds all graphs with defaultBuild: true');
@@ -342,6 +349,8 @@ async function handleBuild(args) {
342
349
  console.log(' aiwg index build');
343
350
  console.log(' aiwg index build --force');
344
351
  console.log(' aiwg index build --graph codebase --force');
352
+ console.log(' aiwg index build --graph source --force');
353
+ console.log(' aiwg index build --graph user --force');
345
354
  console.log(' aiwg index build --graph references # user-defined graph');
346
355
  console.log(' aiwg index build --scope documentation/references');
347
356
  console.log(' aiwg index build --all');
@@ -363,6 +372,7 @@ async function handleBuild(args) {
363
372
  }
364
373
  // Load user-defined graphs
365
374
  loadUserGraphConfigs(cwd);
375
+ loadGlobalGraphConfigs();
366
376
  let jsonBuilt = false;
367
377
  if (graph) {
368
378
  // --graph X: build the JSON graph if X is one; otherwise X may be a
@@ -821,6 +831,83 @@ async function handleSync(args) {
821
831
  process.exit(1);
822
832
  }
823
833
  }
834
+ /**
835
+ * Handle 'index migrate-legacy' command.
836
+ */
837
+ async function handleMigrateLegacy(args) {
838
+ if (args.includes('--help') || args.includes('-h')) {
839
+ console.log('Usage: aiwg index migrate-legacy [--scope project|user|global | --all] [options]');
840
+ console.log('');
841
+ console.log('Migrates compatible legacy root index files into graph sidecar index');
842
+ console.log('directories. Project scope moves .aiwg/.index/*.json to');
843
+ console.log('.aiwg/.index/project/*.json and refreshes the Fortemi Core static');
844
+ console.log('cache. User/global scopes report or migrate their corresponding');
845
+ console.log('sidecar locations without modifying packaged/prebuilt AIWG indexes.');
846
+ console.log('');
847
+ console.log('Options:');
848
+ console.log(' --scope <name> Scope to migrate: project, user, or global (default: project)');
849
+ console.log(' --all Migrate project, user, and global scopes');
850
+ console.log(' --dry-run Print planned changes without writing files');
851
+ console.log(' --no-fortemi-sync Do not refresh the project Fortemi Core static cache');
852
+ console.log(' --generated-at <iso> Override generated timestamp for deterministic fixtures');
853
+ console.log(' --json Print the migration report as JSON');
854
+ console.log('');
855
+ console.log('Examples:');
856
+ console.log(' aiwg index migrate-legacy --scope project --dry-run');
857
+ console.log(' aiwg index migrate-legacy --all --json');
858
+ return;
859
+ }
860
+ const all = args.includes('--all');
861
+ const scopeValue = parseFlagValue(args, '--scope', 'Error: --scope requires project, user, or global');
862
+ if (all && scopeValue) {
863
+ console.error('Error: pass either --all or --scope, not both');
864
+ process.exit(1);
865
+ }
866
+ const allowedScopes = ['project', 'user', 'global'];
867
+ const scopes = all
868
+ ? [...allowedScopes]
869
+ : scopeValue
870
+ ? [scopeValue]
871
+ : ['project'];
872
+ const invalidScope = scopes.find((scope) => !allowedScopes.includes(scope));
873
+ if (invalidScope) {
874
+ console.error('Error: --scope must be project, user, or global');
875
+ process.exit(1);
876
+ }
877
+ const generatedAt = parseFlagValue(args, '--generated-at', 'Error: --generated-at requires an ISO timestamp value');
878
+ const { migrateLegacyIndex } = await import('./legacy-index-migration.js');
879
+ try {
880
+ const report = migrateLegacyIndex(process.cwd(), {
881
+ scopes: scopes,
882
+ dryRun: args.includes('--dry-run'),
883
+ syncFortemi: !args.includes('--no-fortemi-sync'),
884
+ generatedAt,
885
+ });
886
+ if (args.includes('--json')) {
887
+ console.log(JSON.stringify(report, null, 2));
888
+ return;
889
+ }
890
+ console.log(`Legacy index migration ${report.dryRun ? '(DRY RUN)' : 'complete'}`);
891
+ for (const result of report.results) {
892
+ const entries = result.entries === null ? 'unknown' : String(result.entries);
893
+ const detail = result.reason ? ` — ${result.reason}` : '';
894
+ console.log(` ${result.scope}: ${result.status} (${entries} entries)${detail}`);
895
+ for (const file of result.files) {
896
+ console.log(` ${file.name}: ${file.status}`);
897
+ }
898
+ if (result.fortemiCore) {
899
+ console.log(` fortemi-core: ${result.fortemiCore.status} (${result.fortemiCore.itemCount} item(s)) → ${result.fortemiCore.exportPath}`);
900
+ }
901
+ }
902
+ if (report.reportPath) {
903
+ console.log(` report: ${report.reportPath}`);
904
+ }
905
+ }
906
+ catch (err) {
907
+ console.error('Error: ' + (err instanceof Error ? err.message : String(err)));
908
+ process.exit(1);
909
+ }
910
+ }
824
911
  /**
825
912
  * Handle 'index deps' command
826
913
  *
@@ -1027,13 +1114,13 @@ async function handleDiscover(args) {
1027
1114
  if (!phrase) {
1028
1115
  console.error('Error: aiwg index discover requires a search phrase');
1029
1116
  console.log('');
1030
- console.log('Usage: aiwg index discover "<phrase>" [--type <kinds>] [--limit N] [--json] [--graph <name>] [--backend local|fortemi-core]');
1117
+ console.log('Usage: aiwg index discover "<phrase>" [--type <kinds>] [--limit N] [--json|--format json|text] [--pretty|--compact] [--graph <name>] [--backend local|fortemi-core]');
1031
1118
  console.log('');
1032
1119
  console.log('Examples:');
1033
1120
  console.log(' aiwg index discover "create intake"');
1034
1121
  console.log(' aiwg index discover "deploy production" --limit 5');
1035
1122
  console.log(' aiwg index discover "audit security" --type skill,agent');
1036
- console.log(' aiwg index discover "intake" --json');
1123
+ console.log(' aiwg index discover "intake" --format json --pretty');
1037
1124
  process.exit(1);
1038
1125
  }
1039
1126
  const typeValue = parseFlagValue(flags, '--type', 'Error: --type requires a value');
@@ -1043,7 +1130,17 @@ async function handleDiscover(args) {
1043
1130
  .filter(Boolean);
1044
1131
  // K=5 default — see query-engine.ts comment (#1218 Wave A).
1045
1132
  const limit = parsePositiveIntegerFlag(flags, '--limit', 5, 'Error: --limit must be a positive integer');
1046
- const json = flags.includes('--json');
1133
+ const format = parseFlagValue(flags, '--format', 'Error: --format requires text or json') ?? 'text';
1134
+ if (format !== 'text' && format !== 'json') {
1135
+ console.error('Error: --format must be text or json');
1136
+ process.exit(1);
1137
+ }
1138
+ const json = flags.includes('--json') || format === 'json';
1139
+ if (flags.includes('--pretty') && flags.includes('--compact')) {
1140
+ console.error('Error: --pretty and --compact cannot be used together');
1141
+ process.exit(1);
1142
+ }
1143
+ const jsonPretty = !flags.includes('--compact');
1047
1144
  const graph = parseGraphFlag(flags);
1048
1145
  const backend = parseSearchBackendFlag(flags);
1049
1146
  await discoverCapability(cwd, {
@@ -1051,8 +1148,10 @@ async function handleDiscover(args) {
1051
1148
  typeFilter,
1052
1149
  limit,
1053
1150
  json,
1151
+ jsonPretty,
1054
1152
  graph,
1055
1153
  backend,
1154
+ includePaths: false,
1056
1155
  });
1057
1156
  }
1058
1157
  /**
@@ -1069,7 +1168,7 @@ async function handleDiscover(args) {
1069
1168
  * the artifact body so consumers don't need to navigate the filesystem.
1070
1169
  */
1071
1170
  async function handleShow(args) {
1072
- const { showArtifact } = await import('./query-engine.js');
1171
+ const { showArtifact, showMetadata } = await import('./query-engine.js');
1073
1172
  const cwd = process.cwd();
1074
1173
  const positional = [];
1075
1174
  const flags = [];
@@ -1086,6 +1185,7 @@ async function handleShow(args) {
1086
1185
  const HELP_TEXT = [
1087
1186
  '',
1088
1187
  'Usage: aiwg show <type> <name> [--json] [--first] [--graph <name>] [--backend local|fortemi-core]',
1188
+ ' aiwg show metadata <id-or-name-or-path> [--json] [--first] [--graph <name>] [--backend local|fortemi-core]',
1089
1189
  ' aiwg index show <type> <name> ...',
1090
1190
  '',
1091
1191
  'Types: skill | agent | command | rule',
@@ -1093,16 +1193,22 @@ async function handleShow(args) {
1093
1193
  'Examples:',
1094
1194
  ' aiwg show skill intake-wizard',
1095
1195
  ' aiwg show skill flow-deploy-to-production --json',
1196
+ ' aiwg show metadata aiwg:skill:4840fa441622f676 --json',
1096
1197
  ' aiwg show agent aiwg-steward',
1097
1198
  ' aiwg show command discover',
1098
1199
  '',
1099
- 'Tip: use `aiwg discover "<phrase>"` first to find the right name.',
1200
+ 'Tip: use `aiwg discover "<phrase>" --json` first to find the stable id.',
1100
1201
  ].join('\n');
1101
1202
  if (positional.length === 0) {
1102
1203
  console.error('Error: aiwg show requires a type and name');
1103
1204
  console.error(HELP_TEXT);
1104
1205
  process.exit(1);
1105
1206
  }
1207
+ const firstLower = positional[0].toLowerCase();
1208
+ const metadataMode = firstLower === 'metadata';
1209
+ if (metadataMode) {
1210
+ positional.shift();
1211
+ }
1106
1212
  // Wave A (#1218): if the first positional is a known type, treat it
1107
1213
  // as the type. If it's NOT a known type, fall through to single-name
1108
1214
  // mode — `aiwg show intake-wizard` works as long as the name is
@@ -1110,8 +1216,15 @@ async function handleShow(args) {
1110
1216
  // with the disambiguation list (existing behavior in showArtifact).
1111
1217
  let type = null;
1112
1218
  let name;
1113
- const firstLower = positional[0].toLowerCase();
1114
- if (ALLOWED_TYPES.includes(firstLower)) {
1219
+ if (metadataMode) {
1220
+ name = positional.join(' ').trim();
1221
+ if (!name) {
1222
+ console.error('Error: aiwg show metadata requires an id, name, or path');
1223
+ console.error(HELP_TEXT);
1224
+ process.exit(1);
1225
+ }
1226
+ }
1227
+ else if (ALLOWED_TYPES.includes(firstLower)) {
1115
1228
  type = firstLower;
1116
1229
  name = positional.slice(1).join(' ').trim();
1117
1230
  if (!name) {
@@ -1139,13 +1252,17 @@ async function handleShow(args) {
1139
1252
  }
1140
1253
  const graph = parseGraphFlag(flags);
1141
1254
  const backend = parseSearchBackendFlag(flags);
1142
- await showArtifact(cwd, {
1255
+ const params = {
1143
1256
  name,
1144
1257
  typeFilter: type ? [type] : undefined,
1145
1258
  json,
1146
1259
  first,
1147
1260
  graph,
1148
1261
  backend,
1149
- });
1262
+ };
1263
+ if (metadataMode)
1264
+ await showMetadata(cwd, params);
1265
+ else
1266
+ await showArtifact(cwd, params);
1150
1267
  }
1151
1268
  //# sourceMappingURL=cli.js.map
@@ -85,7 +85,7 @@ function matchesPath(recordPath, pattern) {
85
85
  }
86
86
  return recordPath.includes(pattern);
87
87
  }
88
- function loadFortemiCoreExport(cwd, graph = "project") {
88
+ export function loadFortemiCoreExport(cwd, graph = "project") {
89
89
  let status = getFortemiCoreSyncStatus(cwd, graph);
90
90
  if ((!status.optedIn || !status.built || status.stale) && graph === "framework") {
91
91
  const prebuilt = getFortemiCorePrebuiltStatus(graph);
@@ -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 => path.join(cwd, 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 = path.relative(cwd, fullPath);
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 = path.join(cwd, entryPath);
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 = path.join(cwd, entryPath);
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 ? `${INDEX_DIR}/${graph}/` : `${INDEX_DIR}/`;
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)) {