aiwg 2026.7.16 → 2026.7.18

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 (116) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +25 -4
  3. package/dist/src/agents/grounding/knowledge-base.js +3 -2
  4. package/dist/src/api/index.d.ts +10 -0
  5. package/dist/src/api/index.js +10 -0
  6. package/dist/src/artifacts/audit/cli.js +2 -1
  7. package/dist/src/artifacts/browser-export.js +3 -0
  8. package/dist/src/artifacts/cli.js +118 -3
  9. package/dist/src/artifacts/fortemi-core-query-adapter.js +89 -4
  10. package/dist/src/artifacts/fortemi-core-sync.js +2 -2
  11. package/dist/src/artifacts/hybrid-query.js +2 -1
  12. package/dist/src/artifacts/index-builder.js +46 -20
  13. package/dist/src/artifacts/index-reader.js +5 -5
  14. package/dist/src/artifacts/index-status.js +2 -2
  15. package/dist/src/artifacts/legacy-index-migration.js +3 -3
  16. package/dist/src/artifacts/move.js +154 -0
  17. package/dist/src/artifacts/operational-state.js +239 -0
  18. package/dist/src/artifacts/prebuilt-build-lock.js +49 -0
  19. package/dist/src/artifacts/query-engine.js +232 -25
  20. package/dist/src/artifacts/stats.js +3 -3
  21. package/dist/src/artifacts/types.js +34 -6
  22. package/dist/src/artifacts/watcher.js +6 -4
  23. package/dist/src/cli/cli-extension-loader.js +3 -2
  24. package/dist/src/cli/command-log.js +6 -2
  25. package/dist/src/cli/find-package-root.js +4 -4
  26. package/dist/src/cli/handlers/artifacts.js +76 -0
  27. package/dist/src/cli/handlers/best-practices-audit.js +2 -1
  28. package/dist/src/cli/handlers/execution-mode.js +3 -3
  29. package/dist/src/cli/handlers/help.js +2 -0
  30. package/dist/src/cli/handlers/index.js +4 -2
  31. package/dist/src/cli/handlers/init.js +2 -2
  32. package/dist/src/cli/handlers/local-executor.js +2 -2
  33. package/dist/src/cli/handlers/ralph-launcher.js +3 -2
  34. package/dist/src/cli/handlers/regenerate.js +4 -3
  35. package/dist/src/cli/handlers/serve.js +6 -5
  36. package/dist/src/cli/handlers/setup.js +2 -2
  37. package/dist/src/cli/handlers/subcommands.js +6 -4
  38. package/dist/src/cli/handlers/team.js +3 -2
  39. package/dist/src/cli/handlers/use.js +123 -2
  40. package/dist/src/cli/handlers/utilities.js +2 -1
  41. package/dist/src/cli/help-generator.js +1 -0
  42. package/dist/src/cli/scope-resolver.js +31 -1
  43. package/dist/src/cli/skill-usage.js +6 -2
  44. package/dist/src/cli/workspace-signals.js +9 -6
  45. package/dist/src/community/milestones.js +2 -1
  46. package/dist/src/config/aiwg-config.js +10 -7
  47. package/dist/src/config/project-artifacts.js +76 -0
  48. package/dist/src/contributors/discover.js +3 -2
  49. package/dist/src/extensions/commands/definitions.js +33 -5
  50. package/dist/src/extensions/project-local-discovery.js +75 -85
  51. package/dist/src/extensions/project-local-doctor.js +14 -6
  52. package/dist/src/extensions/project-local-paths.js +121 -0
  53. package/dist/src/extensions/project-local-promote.js +1 -1
  54. package/dist/src/extensions/project-local-scaffold.js +3 -8
  55. package/dist/src/extensions/project-quickref.js +6 -7
  56. package/dist/src/issues/local.js +2 -1
  57. package/dist/src/lint/loader.js +2 -1
  58. package/dist/src/mcp/helpers.mjs +58 -3
  59. package/dist/src/mcp/server.mjs +3 -28
  60. package/dist/src/memory/project-registry.js +6 -5
  61. package/dist/src/models/provider-policy.js +1 -0
  62. package/dist/src/ops/registry.js +2 -1
  63. package/dist/src/policy/authorization.js +3 -2
  64. package/dist/src/policy/repo-access.js +5 -4
  65. package/dist/src/providers/capability-matrix.js +6 -3
  66. package/dist/src/release/plan.js +2 -1
  67. package/dist/src/resources/index.d.ts +2 -0
  68. package/dist/src/resources/index.js +2 -0
  69. package/dist/src/resources/web-release.d.ts +89 -0
  70. package/dist/src/resources/web-release.js +886 -0
  71. package/dist/src/rlm/cli.js +3 -2
  72. package/dist/src/serve/executor-registry.js +2 -1
  73. package/dist/src/serve/sandbox-registry.js +2 -1
  74. package/dist/src/smiths/context-pipeline/finalization.js +10 -2
  75. package/dist/src/smiths/context-pipeline/workspace-context.js +31 -16
  76. package/dist/src/storage/config.js +6 -6
  77. package/dist/src/writing/scoring-config-loader.js +2 -1
  78. package/docs/.public/blog/2026-07-aiwg-workflow-portability-cli.svg +71 -0
  79. package/docs/.public/blog/2026-07-aiwg-workflow-portability-hero.png +0 -0
  80. package/docs/CLI_USAGE.md +37 -0
  81. package/docs/_manifest.json +35 -1
  82. package/docs/agentic-install-runbook.md +18 -5
  83. package/docs/blog/2026-07-aiwg-workflow-portability.md +143 -0
  84. package/docs/blog/_manifest.json +1 -0
  85. package/docs/cli-reference.md +388 -302
  86. package/docs/cockpit/README.md +3 -3
  87. package/docs/cockpit/architecture.md +10 -10
  88. package/docs/cockpit/bridge-api.md +16 -4
  89. package/docs/cockpit/daily-operator-gate.md +155 -0
  90. package/docs/cockpit/development.md +1 -0
  91. package/docs/cockpit/recovery.md +7 -1
  92. package/docs/cockpit/sessions.md +2 -2
  93. package/docs/cockpit/trust-and-security.md +20 -2
  94. package/docs/configuration/aiwg-config.md +24 -3
  95. package/docs/contributing/ci-cd-secrets.md +3 -0
  96. package/docs/contributing/publishing-blog-posts.md +18 -0
  97. package/docs/contributing/versioning.md +40 -14
  98. package/docs/customization/project-local-lifecycle.md +29 -3
  99. package/docs/customization/project-quickrefs.md +6 -3
  100. package/docs/customization/user-scope-deployment.md +29 -1
  101. package/docs/development/_manifest.json +1 -0
  102. package/docs/development/aiwg-development-guide.md +15 -4
  103. package/docs/development/private-aiwg-corpus.md +73 -0
  104. package/docs/fortemi-core-prebuilt-indices.md +27 -19
  105. package/docs/install/_manifest.json +9 -0
  106. package/docs/install/web-backed-resources.md +138 -0
  107. package/docs/integrations/fortemi-index-export.md +47 -0
  108. package/docs/releases/v2026.7.17-announcement.md +73 -0
  109. package/docs/releases/v2026.7.18-announcement.md +64 -0
  110. package/package.json +21 -2
  111. package/prebuilt/fortemi-core/framework/aiwg-fortemi-index-v2.json +1 -1
  112. package/prebuilt/fortemi-core/framework/manifest.json +2 -2
  113. package/tools/cli/check-install-size.mjs +2 -2
  114. package/tools/docs/promote-scheduled-posts.mjs +158 -0
  115. package/tools/docs/write-blog-static-pages.mjs +108 -0
  116. package/tools/workspace/check-marketplace-version.mjs +24 -6
package/LICENSE CHANGED
@@ -18,4 +18,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
18
  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
19
  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
20
  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
21
+ SOFTWARE.
package/README.md CHANGED
@@ -6,13 +6,26 @@
6
6
 
7
7
  **Multi-agent AI framework for Claude Code, Copilot, Cursor, Warp, and 6 more platforms**
8
8
 
9
- 200+ agents, 67+ CLI commands, 400+ deployable agent/skill/command/rule artifacts, 8 core frameworks, 27 addons, and a training marketplace package. SDLC workflows, digital forensics, research management, marketing operations, media curation, ops infrastructure, knowledge base, and fine-tuning dataset curation — all deployable with one command.
9
+ 200+ agents, 109+ CLI commands, 400+ deployable agent/skill/command/rule artifacts, 8 core frameworks, 27 addons, and a training marketplace package. SDLC workflows, digital forensics, research management, marketing operations, media curation, ops infrastructure, knowledge base, and fine-tuning dataset curation — all deployable with one command.
10
10
 
11
11
  ```bash
12
- npm i -g aiwg # install globally
12
+ npm i -g aiwg # full CLI and local resource corpus
13
13
  aiwg use sdlc # deploy SDLC framework
14
14
  ```
15
15
 
16
+ For a smaller CLI install that resolves signed, versioned resources from the
17
+ release host:
18
+
19
+ ```bash
20
+ npm i -g @aiwg/cli
21
+ aiwg discover "architecture evolution"
22
+ aiwg show skill architecture-evolution
23
+ ```
24
+
25
+ See [Web-Backed AIWG Resources](docs/install/web-backed-resources.md) for source
26
+ selection, exact-version overrides, cache verification, offline use, and the
27
+ current framework-graph constraints.
28
+
16
29
  Then ask your AI assistant to set up the project for AIWG. The agent-led setup
17
30
  conversation should establish remotes, issue storage, delivery behavior,
18
31
  signing policy, and provider choices; the assistant may call `aiwg setup project`
@@ -111,10 +124,18 @@ Around that core, AIWG ships agent-facing utilities for things the base platform
111
124
 
112
125
  ### Project scope (recommended) vs user scope (global)
113
126
 
114
- `aiwg use` writes artifacts at one of two scopes. Both are first-class supported (see ADR-NUA-001 in `.aiwg/studies/novice-user-adoption/`):
127
+ `aiwg use` supports project deployments, additive user mirrors, and a
128
+ user-global bootstrap:
115
129
 
116
130
  - **Project scope** — default. Run `aiwg use sdlc` from a project root and the artifacts land in `./.claude/agents/`, `./.claude/skills/`, etc. One project's agent set never bleeds into another's session. **This is the recommended default for most use cases.**
117
- - **User scope (global install)** — `aiwg use sdlc --scope user` writes to `~/.claude/agents/`, `~/.claude/skills/`, etc. Same artifact set loads into every session, regardless of project. Fits "AIWG in every conversation" workflows and is the canonical mode for OpenClaw and Hermes (whose primary discovery is user-scope).
131
+ - **User scope (additive mirror)** — `aiwg use sdlc --scope user` keeps the
132
+ project deployment and mirrors it to `~/.claude/agents/`,
133
+ `~/.claude/skills/`, etc.
134
+ - **Global bootstrap** — `aiwg use sdlc --provider claude --global` installs
135
+ framework and kernel assets in native user-level paths while leaving only
136
+ lightweight context and provider bootstrap files in the current project.
137
+ Use `aiwg regenerate --provider <name>` to wire additional projects without
138
+ deploying their own skill copies.
118
139
 
119
140
  The trade-off is real: when the same agent set loads into every session, context from one project can bleed into reasoning about another. Research (REF-720, *Lost in Multi-Turn Conversation*, MSR/Salesforce 2025) measured a 39% capability drop when this happens. The non-blocking project-isolation warning surfaces the trade-off at deploy time so the scope choice is informed. Neither scope is wrong; pick the one that fits the workflow.
120
141
 
@@ -6,6 +6,7 @@
6
6
  */
7
7
  import { promises as fs } from 'fs';
8
8
  import path from 'path';
9
+ import { projectAiwgPath } from '../../config/project-artifacts.js';
9
10
  // ============================================================================
10
11
  // KnowledgeStore
11
12
  // ============================================================================
@@ -20,7 +21,7 @@ export class KnowledgeStore {
20
21
  * Load knowledge bases from .aiwg/knowledge/
21
22
  */
22
23
  async load() {
23
- const knowledgeDir = path.join(this.projectPath, '.aiwg/knowledge');
24
+ const knowledgeDir = projectAiwgPath(this.projectPath, 'knowledge');
24
25
  try {
25
26
  const domains = await fs.readdir(knowledgeDir);
26
27
  for (const domain of domains) {
@@ -73,7 +74,7 @@ export class KnowledgeStore {
73
74
  existing.entries.push(...entries);
74
75
  this.bases.set(domain, existing);
75
76
  // Persist
76
- const domainDir = path.join(this.projectPath, '.aiwg/knowledge', domain);
77
+ const domainDir = projectAiwgPath(this.projectPath, 'knowledge', domain);
77
78
  await fs.mkdir(domainDir, { recursive: true });
78
79
  const outFile = path.join(domainDir, path.basename(filePath));
79
80
  await fs.copyFile(filePath, outFile);
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Supported programmatic entry points for the installed AIWG package.
3
+ *
4
+ * `run()` dispatches through the same router used by `bin/aiwg.mjs`. Resource
5
+ * helpers expose the signed web-release contract without requiring callers to
6
+ * import private `dist/` paths.
7
+ */
8
+ export { run } from '../cli/router.js';
9
+ export * from '../resources/index.js';
10
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Supported programmatic entry points for the installed AIWG package.
3
+ *
4
+ * `run()` dispatches through the same router used by `bin/aiwg.mjs`. Resource
5
+ * helpers expose the signed web-release contract without requiring callers to
6
+ * import private `dist/` paths.
7
+ */
8
+ export { run } from '../cli/router.js';
9
+ export * from '../resources/index.js';
10
+ //# sourceMappingURL=index.js.map
@@ -22,6 +22,7 @@ import { existsSync, readFileSync } from 'node:fs';
22
22
  import { join } from 'node:path';
23
23
  import { computeContentHash, list as listEnrichments, resolveSemanticRoot, get as getEnrichment, } from '../enrichment/store.js';
24
24
  import { validateEnrichmentOutput } from '../enrichment/prompt.js';
25
+ import { projectAiwgPath } from '../../config/project-artifacts.js';
25
26
  import { detectDrift } from './drift.js';
26
27
  import { DEFAULT_THRESHOLDS } from './types.js';
27
28
  export async function main(args) {
@@ -115,7 +116,7 @@ export async function main(args) {
115
116
  }
116
117
  function loadThresholds() {
117
118
  // Try .aiwg/index/audit.config.yaml → fall back to defaults
118
- const path = join(process.cwd(), '.aiwg', 'index', 'audit.config.yaml');
119
+ const path = projectAiwgPath(process.cwd(), 'index', 'audit.config.yaml');
119
120
  if (!existsSync(path))
120
121
  return DEFAULT_THRESHOLDS;
121
122
  try {
@@ -505,6 +505,9 @@ function recordForEntry(cwd, entry, graphName, dependencyGraph, privacy, schemaV
505
505
  embeddings: [],
506
506
  skos_concepts: skos?.concepts ?? [],
507
507
  skos_relations: skos?.relations ?? [],
508
+ ...(entry.operationalState
509
+ ? { operational_state: entry.operationalState }
510
+ : {}),
508
511
  }
509
512
  : {}),
510
513
  updated_at: entry.updated,
@@ -16,8 +16,40 @@
16
16
  * @source @src/cli/handlers/subcommands.ts
17
17
  * @tests @test/unit/artifacts/cli.test.ts
18
18
  */
19
+ import path from 'node:path';
20
+ import { readFileSync } from 'node:fs';
21
+ import { fileURLToPath } from 'node:url';
19
22
  import { GRAPH_CONFIGS, OPERATIONAL_DISCOVERY_TYPES, OPERATIONAL_SHOW_TYPES, isOperationalShowType, loadUserGraphConfigs, loadGlobalGraphConfigs, orderedGraphEntries, } from './types.js';
20
23
  import { SUPPORTED_VIEWS } from './corpus-views/renderers.js';
24
+ import { parseResourceSelector, readVerifiedRegularFile, } from '../resources/web-release.js';
25
+ import { findPackageRoot } from '../cli/find-package-root.js';
26
+ const MAX_RESOURCE_TRUST_ROOT_BYTES = 64 * 1024;
27
+ function webReleaseOptionsFromEnvironment() {
28
+ const baseUrl = process.env.AIWG_RESOURCE_BASE_URL;
29
+ const cacheRoot = process.env.AIWG_RESOURCE_CACHE_ROOT;
30
+ const trustRootFile = process.env.AIWG_RESOURCE_TRUST_ROOT_FILE;
31
+ let publicKeyPem;
32
+ if (trustRootFile !== undefined) {
33
+ if (trustRootFile.trim().length === 0) {
34
+ throw new Error('AIWG_RESOURCE_TRUST_ROOT_FILE must name a non-empty public PEM file');
35
+ }
36
+ publicKeyPem = readVerifiedRegularFile(path.resolve(trustRootFile), {
37
+ label: 'AIWG_RESOURCE_TRUST_ROOT_FILE public PEM',
38
+ maxBytes: MAX_RESOURCE_TRUST_ROOT_BYTES,
39
+ });
40
+ if (publicKeyPem.toString('utf8').trim().length === 0) {
41
+ throw new Error('AIWG_RESOURCE_TRUST_ROOT_FILE public PEM is empty');
42
+ }
43
+ }
44
+ return {
45
+ ...(baseUrl === undefined ? {} : { baseUrl }),
46
+ ...(cacheRoot === undefined ? {} : { cacheRoot }),
47
+ ...(publicKeyPem === undefined ? {} : { publicKeyPem }),
48
+ ...(process.env.AIWG_RESOURCE_ALLOW_INSECURE_LOOPBACK_HTTP === '1'
49
+ ? { allowInsecureLoopbackHttp: true }
50
+ : {}),
51
+ };
52
+ }
21
53
  /** Parse --graph flag from args, returns undefined for "all graphs" */
22
54
  function parseGraphFlag(args) {
23
55
  const idx = args.indexOf('--graph');
@@ -53,6 +85,67 @@ function parseBackendFlag(args) {
53
85
  function parseSearchBackendFlag(args) {
54
86
  return parseBackendFlag(args) ?? 'fortemi-core';
55
87
  }
88
+ /**
89
+ * The full `aiwg` package owns a local corpus, while `@aiwg/cli` intentionally
90
+ * ships only executable code and embedded runtime metadata. Select web
91
+ * resources automatically for the lightweight distribution so a normal
92
+ * `aiwg discover` / `aiwg show` invocation works immediately after install.
93
+ *
94
+ * Resolve from this module rather than process.argv so the exported API gets
95
+ * the same package-aware default as the npm binary.
96
+ */
97
+ function defaultResourceSource() {
98
+ const packageRoot = findPackageRoot(path.dirname(fileURLToPath(import.meta.url)));
99
+ if (!packageRoot)
100
+ return 'local';
101
+ try {
102
+ const manifest = JSON.parse(readFileSync(path.join(packageRoot, 'package.json'), 'utf8'));
103
+ return manifest.name === '@aiwg/cli' ? 'web' : 'local';
104
+ }
105
+ catch {
106
+ return 'local';
107
+ }
108
+ }
109
+ function parseResourceSourceFlag(args) {
110
+ const indices = args
111
+ .map((arg, index) => arg === '--resource-source' ? index : -1)
112
+ .filter((index) => index >= 0);
113
+ if (indices.length > 1) {
114
+ console.error('Error: --resource-source may be specified only once');
115
+ process.exit(1);
116
+ }
117
+ if (indices.length === 0)
118
+ return defaultResourceSource();
119
+ const value = args[indices[0] + 1];
120
+ if (value === 'local' || value === 'web' || value === 'auto')
121
+ return value;
122
+ console.error('Error: --resource-source must be local, web, or auto');
123
+ process.exit(1);
124
+ }
125
+ function parseAiwgVersionFlag(args) {
126
+ const indices = args
127
+ .map((arg, index) => arg === '--aiwg-version' ? index : -1)
128
+ .filter((index) => index >= 0);
129
+ if (indices.length > 1) {
130
+ console.error('Error: --aiwg-version may be specified only once');
131
+ process.exit(1);
132
+ }
133
+ if (indices.length === 0)
134
+ return undefined;
135
+ const value = args[indices[0] + 1];
136
+ if (!value || value.startsWith('--')) {
137
+ console.error('Error: --aiwg-version requires an exact calendar-semver version or channel name');
138
+ process.exit(1);
139
+ }
140
+ try {
141
+ parseResourceSelector(value);
142
+ }
143
+ catch (error) {
144
+ console.error(`Error: ${error instanceof Error ? error.message : String(error)}`);
145
+ process.exit(1);
146
+ }
147
+ return value;
148
+ }
56
149
  function firstPositionalArg(args, valueFlags) {
57
150
  const skip = new Set();
58
151
  for (let i = 0; i < args.length; i++) {
@@ -1215,13 +1308,14 @@ async function handleDiscover(args) {
1215
1308
  if (!phrase) {
1216
1309
  console.error('Error: aiwg index discover requires a search phrase');
1217
1310
  console.log('');
1218
- console.log('Usage: aiwg index discover "<phrase>" [--type <kinds>] [--limit N] [--json|--format json|text] [--pretty|--compact] [--graph <name>] [--backend local|fortemi-core]');
1311
+ console.log('Usage: aiwg index discover "<phrase>" [--type <kinds>] [--limit N] [--json|--format json|text] [--pretty|--compact] [--graph <name>] [--backend local|fortemi-core] [--resource-source local|web|auto] [--aiwg-version <exact-or-channel>] [--offline]');
1219
1312
  console.log('');
1220
1313
  console.log('Examples:');
1221
1314
  console.log(' aiwg index discover "create intake"');
1222
1315
  console.log(' aiwg index discover "deploy production" --limit 5');
1223
1316
  console.log(` aiwg index discover "audit security" --type ${OPERATIONAL_DISCOVERY_TYPES.join(',')}`);
1224
1317
  console.log(' aiwg index discover "intake" --format json --pretty');
1318
+ console.log(' aiwg discover "intake" --resource-source web --aiwg-version stable');
1225
1319
  process.exit(1);
1226
1320
  }
1227
1321
  const typeValue = parseFlagValue(flags, '--type', 'Error: --type requires a value');
@@ -1244,6 +1338,12 @@ async function handleDiscover(args) {
1244
1338
  const jsonPretty = !flags.includes('--compact');
1245
1339
  const graph = parseGraphFlag(flags);
1246
1340
  const backend = parseSearchBackendFlag(flags);
1341
+ const resourceSource = parseResourceSourceFlag(flags);
1342
+ const aiwgVersion = parseAiwgVersionFlag(flags);
1343
+ const offline = flags.includes('--offline');
1344
+ const webReleaseOptions = resourceSource === 'local'
1345
+ ? undefined
1346
+ : webReleaseOptionsFromEnvironment();
1247
1347
  await discoverCapability(cwd, {
1248
1348
  phrase,
1249
1349
  typeFilter,
@@ -1252,6 +1352,10 @@ async function handleDiscover(args) {
1252
1352
  jsonPretty,
1253
1353
  graph,
1254
1354
  backend,
1355
+ resourceSource,
1356
+ aiwgVersion,
1357
+ offline,
1358
+ webReleaseOptions,
1255
1359
  includePaths: false,
1256
1360
  });
1257
1361
  }
@@ -1284,8 +1388,8 @@ async function handleShow(args) {
1284
1388
  }
1285
1389
  const HELP_TEXT = [
1286
1390
  '',
1287
- 'Usage: aiwg show <type> <name> [--json] [--first] [--graph <name>] [--backend local|fortemi-core]',
1288
- ' aiwg show metadata <id-or-name-or-path> [--json] [--first] [--graph <name>] [--backend local|fortemi-core]',
1391
+ 'Usage: aiwg show <type> <name> [--json] [--first] [--graph <name>] [--backend local|fortemi-core] [--resource-source local|web|auto] [--aiwg-version <exact-or-channel>] [--offline]',
1392
+ ' aiwg show metadata <id-or-name-or-path> [--json] [--first] [--graph <name>] [--backend local|fortemi-core] [--resource-source local|web|auto] [--aiwg-version <exact-or-channel>] [--offline]',
1289
1393
  ' aiwg index show <type> <name> ...',
1290
1394
  '',
1291
1395
  `Types: ${OPERATIONAL_SHOW_TYPES.join(' | ')}`,
@@ -1299,6 +1403,7 @@ async function handleShow(args) {
1299
1403
  ' aiwg show metadata aiwg:skill:4840fa441622f676 --json',
1300
1404
  ' aiwg show agent aiwg-steward',
1301
1405
  ' aiwg show command discover',
1406
+ ' aiwg show skill intake-wizard --resource-source web --aiwg-version stable',
1302
1407
  '',
1303
1408
  'Tip: use `aiwg discover "<phrase>" --json` first to find the stable id.',
1304
1409
  ].join('\n');
@@ -1355,6 +1460,12 @@ async function handleShow(args) {
1355
1460
  }
1356
1461
  const graph = parseGraphFlag(flags);
1357
1462
  const backend = parseSearchBackendFlag(flags);
1463
+ const resourceSource = parseResourceSourceFlag(flags);
1464
+ const aiwgVersion = parseAiwgVersionFlag(flags);
1465
+ const offline = flags.includes('--offline');
1466
+ const webReleaseOptions = resourceSource === 'local'
1467
+ ? undefined
1468
+ : webReleaseOptionsFromEnvironment();
1358
1469
  const params = {
1359
1470
  name,
1360
1471
  typeFilter: type ? [type] : undefined,
@@ -1362,6 +1473,10 @@ async function handleShow(args) {
1362
1473
  first,
1363
1474
  graph,
1364
1475
  backend,
1476
+ resourceSource,
1477
+ aiwgVersion,
1478
+ offline,
1479
+ webReleaseOptions,
1365
1480
  };
1366
1481
  if (metadataMode)
1367
1482
  await showMetadata(cwd, params);
@@ -1,6 +1,8 @@
1
1
  import fs from "node:fs";
2
+ import { createHash } from "node:crypto";
2
3
  import { bm25Rank } from "./fulltext.js";
3
4
  import { getFortemiCorePrebuiltStatus, getFortemiCoreSyncStatus } from "./fortemi-core-sync.js";
5
+ import { operationalStateQueryProjection, } from "./operational-state.js";
4
6
  function tokenize(text) {
5
7
  return text
6
8
  .toLowerCase()
@@ -59,6 +61,9 @@ function recordToStaticResult(record, score, matched) {
59
61
  summary: record.summary ?? record.search?.summary,
60
62
  score: Math.round(score * 1000) / 1000,
61
63
  matched,
64
+ ...(record.operational_state
65
+ ? { operational_state: operationalStateQueryProjection(record.operational_state) }
66
+ : {}),
62
67
  };
63
68
  }
64
69
  function recordBody(record) {
@@ -85,7 +90,83 @@ function matchesPath(recordPath, pattern) {
85
90
  }
86
91
  return recordPath.includes(pattern);
87
92
  }
88
- export function loadFortemiCoreExport(cwd, graph = "project") {
93
+ function readVerifiedRegularFile(pathname, expectedSize, expectedSha256, label) {
94
+ if (!Number.isSafeInteger(expectedSize) || expectedSize < 0) {
95
+ throw new Error(`${label} has an invalid signed size`);
96
+ }
97
+ const pathStat = fs.lstatSync(pathname);
98
+ if (pathStat.isSymbolicLink() || !pathStat.isFile()) {
99
+ throw new Error(`${label} must be a regular file and may not be a symlink`);
100
+ }
101
+ if (pathStat.size !== expectedSize) {
102
+ throw new Error(`${label} size does not match the signed release descriptor`);
103
+ }
104
+ const noFollow = typeof fs.constants.O_NOFOLLOW === "number"
105
+ ? fs.constants.O_NOFOLLOW
106
+ : 0;
107
+ const fd = fs.openSync(pathname, fs.constants.O_RDONLY | fs.constants.O_NONBLOCK | noFollow);
108
+ try {
109
+ const openedStat = fs.fstatSync(fd);
110
+ if (!openedStat.isFile() || openedStat.size !== expectedSize) {
111
+ throw new Error(`${label} changed before it could be read safely`);
112
+ }
113
+ const bytes = Buffer.alloc(expectedSize + 1);
114
+ let offset = 0;
115
+ while (offset < bytes.length) {
116
+ const count = fs.readSync(fd, bytes, offset, bytes.length - offset, null);
117
+ if (count === 0)
118
+ break;
119
+ offset += count;
120
+ }
121
+ if (offset !== expectedSize) {
122
+ throw new Error(`${label} changed size while being read`);
123
+ }
124
+ const verified = bytes.subarray(0, expectedSize);
125
+ const digest = createHash("sha256").update(verified).digest("hex");
126
+ if (digest !== expectedSha256) {
127
+ throw new Error(`${label} does not match the signed release descriptor`);
128
+ }
129
+ return verified;
130
+ }
131
+ finally {
132
+ fs.closeSync(fd);
133
+ }
134
+ }
135
+ export function loadFortemiCoreExport(cwd, graph = "project", sourcePaths) {
136
+ if (sourcePaths) {
137
+ try {
138
+ const manifestBytes = readVerifiedRegularFile(sourcePaths.manifestPath, sourcePaths.manifestSize, sourcePaths.manifestSha256, "Verified Fortemi Core manifest");
139
+ const exportBytes = readVerifiedRegularFile(sourcePaths.exportPath, sourcePaths.exportSize, sourcePaths.exportSha256, "Verified Fortemi Core export");
140
+ const manifestSha256 = createHash("sha256").update(manifestBytes).digest("hex");
141
+ const exportSha256 = createHash("sha256").update(exportBytes).digest("hex");
142
+ if (manifestSha256 !== sourcePaths.manifestSha256 ||
143
+ exportSha256 !== sourcePaths.exportSha256) {
144
+ return { reason: "Verified Fortemi Core source bytes do not match the signed release descriptor." };
145
+ }
146
+ const manifest = JSON.parse(manifestBytes.toString("utf-8"));
147
+ const exported = JSON.parse(exportBytes.toString("utf-8"));
148
+ if (manifest.schema_version !== "aiwg.fortemi.prebuilt.v1" ||
149
+ manifest.backend !== "fortemi-core" ||
150
+ manifest.graph !== graph ||
151
+ manifest.export_path !== "aiwg-fortemi-index-v2.json" ||
152
+ manifest.export_schema_version !== "aiwg.fortemi.index.export.v2") {
153
+ return { reason: "Verified Fortemi Core source manifest is incompatible with the requested graph." };
154
+ }
155
+ if (manifest.export_checksum !== exportSha256 ||
156
+ exported.schema_version !== "aiwg.fortemi.index.export.v2" ||
157
+ exported.source?.graph !== graph ||
158
+ !Array.isArray(exported.items) ||
159
+ manifest.item_count !== exported.items.length) {
160
+ return { reason: "Verified Fortemi Core source export does not match its manifest." };
161
+ }
162
+ return { exported };
163
+ }
164
+ catch (err) {
165
+ return {
166
+ reason: `Verified Fortemi Core source could not be read: ${err instanceof Error ? err.message : String(err)}`,
167
+ };
168
+ }
169
+ }
89
170
  let status = getFortemiCoreSyncStatus(cwd, graph);
90
171
  if ((!status.optedIn || !status.built || status.stale) && graph === "framework") {
91
172
  const prebuilt = getFortemiCorePrebuiltStatus(graph);
@@ -164,16 +245,17 @@ function entryFromRecord(record) {
164
245
  kernel: typeof record.search?.frontmatter?.kernel === "boolean"
165
246
  ? record.search.frontmatter.kernel
166
247
  : undefined,
248
+ operationalState: record.operational_state,
167
249
  };
168
250
  }
169
- export function loadFortemiCoreMetadataEntries(cwd, graph = "project") {
170
- const loaded = loadFortemiCoreExport(cwd, graph);
251
+ export function loadFortemiCoreMetadataEntries(cwd, graph = "project", sourcePaths) {
252
+ const loaded = loadFortemiCoreExport(cwd, graph, sourcePaths);
171
253
  if (!loaded.exported)
172
254
  return { entries: [], reason: loaded.reason };
173
255
  return { entries: loaded.exported.items.map(entryFromRecord) };
174
256
  }
175
257
  export async function queryFortemiCoreAiwgDiscovery(cwd, options) {
176
- const loaded = loadFortemiCoreExport(cwd, options.graph ?? "framework");
258
+ const loaded = loadFortemiCoreExport(cwd, options.graph ?? "framework", options.sourcePaths);
177
259
  if (!loaded.exported) {
178
260
  return {
179
261
  results: [],
@@ -328,6 +410,9 @@ export function queryFortemiCoreStaticFulltextIndex(cwd, options) {
328
410
  summary: record.summary ?? record.search?.summary ?? record.text,
329
411
  score: Math.round(hit.score * 1000) / 1000,
330
412
  matched: hit.matchedTerms,
413
+ ...(record.operational_state
414
+ ? { operational_state: operationalStateQueryProjection(record.operational_state) }
415
+ : {}),
331
416
  };
332
417
  }),
333
418
  };
@@ -1,7 +1,7 @@
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
+ import { GRAPH_CONFIGS, getProjectIndexRoot, loadGlobalGraphConfigs } from "./types.js";
5
5
  import { buildAiwgFortemiIndexExport, } from "./browser-export.js";
6
6
  import { loadGraphIndexFile } from "./index-reader.js";
7
7
  function syncDir(cwd, graph) {
@@ -11,7 +11,7 @@ function syncDir(cwd, graph) {
11
11
  const xdgData = process.env.XDG_DATA_HOME ?? path.join(process.env.HOME ?? cwd, ".local", "share");
12
12
  return path.join(xdgData, "aiwg", "index", "fortemi-core", graph);
13
13
  }
14
- return path.join(cwd, ".aiwg", ".index", "fortemi-core", graph);
14
+ return path.join(getProjectIndexRoot(cwd), "fortemi-core", graph);
15
15
  }
16
16
  function findPackageRoot(startDir) {
17
17
  let current = startDir;
@@ -9,6 +9,7 @@
9
9
  */
10
10
  import { promises as fs } from 'fs';
11
11
  import path from 'path';
12
+ import { resolveProjectAiwgDir } from '../config/project-artifacts.js';
12
13
  // ============================================================================
13
14
  // ArtifactIndex
14
15
  // ============================================================================
@@ -24,7 +25,7 @@ export class ArtifactIndex {
24
25
  */
25
26
  async build() {
26
27
  this.artifacts = [];
27
- const aiwgDir = path.join(this.projectPath, '.aiwg');
28
+ const aiwgDir = resolveProjectAiwgDir(this.projectPath);
28
29
  try {
29
30
  await this.scanDirectory(aiwgDir, '');
30
31
  }
@@ -13,30 +13,50 @@ 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 { DEFAULT_INDEX_EXTENSIONS, INDEX_EXTRACTOR_VERSION, INDEX_VERSION, INDEX_DIR, OPERATIONAL_DISCOVERY_TYPES, PHASE_DIRECTORIES, GRAPH_CONFIGS, TEMPLATE_INDEX_EXTENSIONS, loadUserGraphConfigs, loadGlobalGraphConfigs, } from './types.js';
16
+ import { DEFAULT_INDEX_EXTENSIONS, INDEX_EXTRACTOR_VERSION, INDEX_VERSION, INDEX_DIR, OPERATIONAL_DISCOVERY_TYPES, PHASE_DIRECTORIES, GRAPH_CONFIGS, TEMPLATE_INDEX_EXTENSIONS, resolveGraphScanDir, 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
20
  import { workspaceLinkedFiles } from '../smiths/context-pipeline/workspace-context.js';
21
- function expandScanDir(cwd, scanDir) {
22
- if (scanDir === '~')
23
- return process.env.HOME ?? scanDir;
24
- if (scanDir.startsWith('~/')) {
25
- return path.join(process.env.HOME ?? '', scanDir.slice(2));
26
- }
27
- if (path.isAbsolute(scanDir))
28
- return scanDir;
29
- return path.join(cwd, scanDir);
21
+ import { normalizeOperationalState } from './operational-state.js';
22
+ import { DEFAULT_PROJECT_AIWG_DIR, resolveProjectAiwgDir, } from '../config/project-artifacts.js';
23
+ function pathContains(parent, child) {
24
+ const relative = path.relative(parent, child);
25
+ return relative === '' || (!relative.startsWith('..') && !path.isAbsolute(relative));
26
+ }
27
+ function toPosixPath(value) {
28
+ return value.split(path.sep).join('/');
30
29
  }
31
- function indexPathFor(cwd, fullPath) {
30
+ function indexPathFor(cwd, fullPath, graph) {
31
+ if (!graph || graph === 'project') {
32
+ const artifactRoot = resolveProjectAiwgDir(cwd);
33
+ if (pathContains(artifactRoot, fullPath)) {
34
+ const relative = toPosixPath(path.relative(artifactRoot, fullPath));
35
+ return relative ? `${DEFAULT_PROJECT_AIWG_DIR}/${relative}` : DEFAULT_PROJECT_AIWG_DIR;
36
+ }
37
+ }
32
38
  const rel = path.relative(cwd, fullPath);
33
39
  if (!rel.startsWith('..') && !path.isAbsolute(rel))
34
- return rel;
40
+ return toPosixPath(rel);
35
41
  return fullPath;
36
42
  }
37
- function absoluteEntryPath(cwd, entryPath) {
43
+ function absoluteEntryPath(cwd, entryPath, graph) {
44
+ if ((!graph || graph === 'project') && entryPath.startsWith(`${DEFAULT_PROJECT_AIWG_DIR}/`)) {
45
+ return path.join(resolveProjectAiwgDir(cwd), entryPath.slice(DEFAULT_PROJECT_AIWG_DIR.length + 1));
46
+ }
38
47
  return path.isAbsolute(entryPath) ? entryPath : path.join(cwd, entryPath);
39
48
  }
49
+ function loadIndexFromDir(indexDir, filename) {
50
+ const filePath = path.join(indexDir, filename);
51
+ if (!fs.existsSync(filePath))
52
+ return null;
53
+ try {
54
+ return JSON.parse(fs.readFileSync(filePath, 'utf-8'));
55
+ }
56
+ catch {
57
+ return null;
58
+ }
59
+ }
40
60
  /**
41
61
  * Parse YAML frontmatter from markdown content
42
62
  */
@@ -664,12 +684,12 @@ export async function buildIndex(cwd, options = {}) {
664
684
  fileExtensions = [...DEFAULT_INDEX_EXTENSIONS];
665
685
  }
666
686
  else if (graphConfig) {
667
- scanDirs = graphConfig.scanDirs.map(d => expandScanDir(cwd, d));
687
+ scanDirs = graphConfig.scanDirs.map(d => resolveGraphScanDir(cwd, d));
668
688
  fileExtensions = graphConfig.extensions;
669
689
  }
670
690
  else {
671
691
  // Default: scan .aiwg/ (backward compatible)
672
- scanDirs = [path.join(cwd, '.aiwg')];
692
+ scanDirs = [resolveProjectAiwgDir(cwd)];
673
693
  fileExtensions = [...DEFAULT_INDEX_EXTENSIONS];
674
694
  }
675
695
  // Verify at least one scan directory exists
@@ -699,7 +719,7 @@ export async function buildIndex(cwd, options = {}) {
699
719
  indexOutputDir = resolveIndexDir(cwd, graph);
700
720
  }
701
721
  else {
702
- indexOutputDir = path.join(cwd, INDEX_DIR);
722
+ indexOutputDir = resolveIndexDir(cwd);
703
723
  }
704
724
  fs.mkdirSync(indexOutputDir, { recursive: true });
705
725
  // effectiveOutputCwd is used for backward-compat loadMetadataIndex calls
@@ -718,7 +738,11 @@ export async function buildIndex(cwd, options = {}) {
718
738
  return;
719
739
  }
720
740
  // Load existing index for incremental updates
721
- const existingIndex = force ? null : loadGraphIndexFile(effectiveOutputCwd, 'metadata.json', graph);
741
+ const existingIndex = force
742
+ ? null
743
+ : outputDir
744
+ ? loadIndexFromDir(indexOutputDir, 'metadata.json')
745
+ : loadGraphIndexFile(effectiveOutputCwd, 'metadata.json', graph);
722
746
  const canReuseExisting = existingIndex?.version === INDEX_VERSION
723
747
  && existingIndex.extractorVersion === INDEX_EXTRACTOR_VERSION;
724
748
  const existingEntries = canReuseExisting ? existingIndex.entries : {};
@@ -762,7 +786,7 @@ export async function buildIndex(cwd, options = {}) {
762
786
  let unchangedCount = 0;
763
787
  const useFilenameMetadata = graphConfig?.nodeStrategy === 'filename-metadata';
764
788
  for (const fullPath of files) {
765
- const relativePath = indexPathFor(cwd, fullPath);
789
+ const relativePath = indexPathFor(cwd, fullPath, graph);
766
790
  let entry;
767
791
  if (useFilenameMetadata) {
768
792
  // Filename-metadata strategy: derive metadata from filename, skip content read.
@@ -857,6 +881,7 @@ export async function buildIndex(cwd, options = {}) {
857
881
  const kernel = data.kernel === true || data.kernel === 'true' ? true : undefined;
858
882
  // Script entrypoint metadata is meaningful for skills only (#1227).
859
883
  const script = type === 'skill' ? extractSkillScript(data) : undefined;
884
+ const operationalState = normalizeOperationalState(data.operational_state);
860
885
  // Canonical short name (#1233) — used by the scorer to floor exact-name
861
886
  // queries to 1.0 so hyphenated kernel-skill names like `aiwg-doctor`
862
887
  // remain searchable even when the rendered title strips the hyphen.
@@ -881,6 +906,7 @@ export async function buildIndex(cwd, options = {}) {
881
906
  ...(searchTerms && searchTerms.length > 0 ? { searchTerms } : {}),
882
907
  ...(kernel ? { kernel } : {}),
883
908
  ...(script ? { script } : {}),
909
+ ...(operationalState ? { operationalState } : {}),
884
910
  };
885
911
  }
886
912
  entries[relativePath] = entry;
@@ -935,7 +961,7 @@ export async function buildIndex(cwd, options = {}) {
935
961
  // Build REF-XXX → path map from all entries with ref frontmatter
936
962
  const entryFrontmatter = new Map();
937
963
  for (const entryPath of Object.keys(entries)) {
938
- const fullPath = absoluteEntryPath(cwd, entryPath);
964
+ const fullPath = absoluteEntryPath(cwd, entryPath, graph);
939
965
  if (fs.existsSync(fullPath)) {
940
966
  const content = fs.readFileSync(fullPath, 'utf-8');
941
967
  const { data } = parseFrontmatter(content);
@@ -946,7 +972,7 @@ export async function buildIndex(cwd, options = {}) {
946
972
  // Parse each entry as a citation sidecar and extract edges
947
973
  let citationEdgeCount = 0;
948
974
  for (const entryPath of Object.keys(entries)) {
949
- const fullPath = absoluteEntryPath(cwd, entryPath);
975
+ const fullPath = absoluteEntryPath(cwd, entryPath, graph);
950
976
  if (!fs.existsSync(fullPath))
951
977
  continue;
952
978
  const content = fs.readFileSync(fullPath, 'utf-8');