@aiwg/cli 2026.9.7 → 2026.9.10

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 (48) hide show
  1. package/bin/aiwg.mjs +55 -10
  2. package/dist/src/artifacts/cli.js +20 -8
  3. package/dist/src/artifacts/corpus-views/taxonomies.js +1 -1
  4. package/dist/src/artifacts/index-builder.js +43 -1
  5. package/dist/src/artifacts/query-engine.js +7 -0
  6. package/dist/src/cli/handlers/help.js +6 -5
  7. package/dist/src/cli/handlers/init.js +7 -2
  8. package/dist/src/cli/handlers/installation.js +102 -2
  9. package/dist/src/cli/handlers/mc.js +87 -17
  10. package/dist/src/cli/handlers/ralph.js +8 -8
  11. package/dist/src/cli/handlers/refresh.js +67 -7
  12. package/dist/src/cli/handlers/repo-access.js +155 -4
  13. package/dist/src/cli/handlers/setup.js +5 -5
  14. package/dist/src/cli/handlers/steward.js +39 -2
  15. package/dist/src/cli/handlers/use.js +13 -4
  16. package/dist/src/cli/handlers/version.js +49 -14
  17. package/dist/src/cli/handlers/workspace-context.js +8 -0
  18. package/dist/src/cli/services/deployment-verification.js +71 -10
  19. package/dist/src/cli/watch-service.js +43 -3
  20. package/dist/src/config/aiwg-config.js +95 -3
  21. package/dist/src/config/cli.js +16 -1
  22. package/dist/src/config/gitignore.js +5 -0
  23. package/dist/src/dataset/fortemi-capability.d.ts +70 -0
  24. package/dist/src/dataset/fortemi-capability.js +211 -0
  25. package/dist/src/dataset/fortemi-dataset-execution.d.ts +9 -0
  26. package/dist/src/dataset/fortemi-dataset-execution.js +49 -8
  27. package/dist/src/dataset/fortemi-run-receipt.d.ts +1 -0
  28. package/dist/src/dataset/fortemi-run-receipt.js +2 -19
  29. package/dist/src/dataset/fortemi-schema-path.d.ts +7 -0
  30. package/dist/src/dataset/fortemi-schema-path.js +24 -0
  31. package/dist/src/dataset/index.d.ts +1 -0
  32. package/dist/src/dataset/index.js +1 -0
  33. package/dist/src/extensions/claude-hooks-installer.js +22 -6
  34. package/dist/src/extensions/project-local-doctor.js +15 -1
  35. package/dist/src/lint/cli.js +5 -1
  36. package/dist/src/lint/reporters.js +17 -0
  37. package/dist/src/lint/runner.js +297 -4
  38. package/dist/src/policy/authorization.js +1 -1
  39. package/dist/src/smiths/context-pipeline/workspace-context.js +138 -49
  40. package/package.json +1 -1
  41. package/schemas/dataset/fortemi-capability-validation/1.0.1/authority.json +61 -0
  42. package/schemas/dataset/fortemi-capability-validation/1.0.1/capability.schema.json +469 -0
  43. package/schemas/security/threat-assessment-report.v1.schema.json +2 -1
  44. package/tools/agents/deploy-agents.mjs +8 -0
  45. package/tools/agents/providers/base.mjs +261 -16
  46. package/tools/agents/providers/claude.mjs +61 -4
  47. package/tools/agents/providers/codex.mjs +9 -0
  48. package/tools/skills/deploy-skills-codex.mjs +121 -7
@@ -33,6 +33,12 @@ const LEGACY_ROOT_FILES = [
33
33
  '.github/copilot-instructions.md',
34
34
  'AIWG.md',
35
35
  ];
36
+ /**
37
+ * Operator-content volume above which a provider-named source is surfaced for a scope
38
+ * decision instead of being routed on filename alone (#2537). A genuine provider adapter
39
+ * is a few hundred bytes; a migrated project contract is tens of KB.
40
+ */
41
+ const SCOPE_REVIEW_BYTES = 4096;
36
42
  const GENERATED_BLOCKS = [
37
43
  [PROVIDER_BOOTSTRAP_START, PROVIDER_BOOTSTRAP_END],
38
44
  ['<!-- AIWG:context-hook:start -->', '<!-- AIWG:context-hook:end -->'],
@@ -381,7 +387,8 @@ export function buildWorkspaceManagedBlock(projectPath, providerFiles = []) {
381
387
  '',
382
388
  '- Edit project-neutral notes only inside the protected Project Context section below.',
383
389
  '- Keep detailed policies, runbooks, hooks, and quickrefs in linked files.',
384
- '- Keep provider-only directives in `.aiwg/context/providers/`.',
390
+ '- `.aiwg/context/providers/` is reference only: no provider bootstrap auto-loads it.',
391
+ ' Put directives you want read into Project Context above.',
385
392
  '- Never store secrets, tokens, credentials, or machine-local sensitive values here.',
386
393
  '',
387
394
  '### Artifact Routing',
@@ -419,11 +426,16 @@ export async function ensureWorkspaceContext(projectPath, options = {}) {
419
426
  const workspacePath = path.join(projectPath, 'WORKSPACE.md');
420
427
  const warnings = [];
421
428
  const existing = await readOptional(workspacePath);
429
+ // `aiwg use` and `aiwg regenerate` rebuild the managed block without
430
+ // knowing about earlier migrations. Rediscover the migrated provider files
431
+ // so their links survive every regeneration instead of only the one that
432
+ // wrote them (#2558).
433
+ const providerFiles = options.providerFiles ?? await migratedProviderFiles(projectPath);
422
434
  if (existing === null) {
423
- await atomicWrite(workspacePath, buildWorkspaceDocument(projectPath, '', options.providerFiles));
435
+ await atomicWrite(workspacePath, buildWorkspaceDocument(projectPath, '', providerFiles));
424
436
  return { path: workspacePath, action: 'created', warnings };
425
437
  }
426
- const block = buildWorkspaceManagedBlock(projectPath, options.providerFiles);
438
+ const block = buildWorkspaceManagedBlock(projectPath, providerFiles);
427
439
  if (existing.includes(WORKSPACE_MANAGED_START) || existing.includes(WORKSPACE_MANAGED_END)) {
428
440
  let updated = replaceBlock(existing, WORKSPACE_MANAGED_START, WORKSPACE_MANAGED_END, block);
429
441
  // A managed project-extraction block explicitly points operators back to
@@ -445,7 +457,7 @@ export async function ensureWorkspaceContext(projectPath, options = {}) {
445
457
  }
446
458
  const backupPath = `${workspacePath}.bak.${new Date().toISOString().replace(/[:.]/g, '-')}`;
447
459
  await fs.writeFile(backupPath, existing, 'utf8');
448
- await atomicWrite(workspacePath, buildWorkspaceDocument(projectPath, existing, options.providerFiles));
460
+ await atomicWrite(workspacePath, buildWorkspaceDocument(projectPath, existing, providerFiles));
449
461
  return { path: workspacePath, action: 'updated', backupPath, warnings };
450
462
  }
451
463
  export function buildProviderBootstrapBlock(provider) {
@@ -689,14 +701,29 @@ export async function auditWorkspaceContext(projectPath) {
689
701
  .filter(([, values]) => new Set(values.map((value) => value.polarity)).size > 1)
690
702
  .map(([key, values]) => ({ key, sources: values.map(({ path: sourcePath, directive }) => ({ path: sourcePath, directive })).sort((a, b) => a.path.localeCompare(b.path)) }));
691
703
  const rootOperator = sources.filter((source) => source.scope === 'root' && source.operatorContent.trim());
692
- // Provider startup roots remain attributed provider context even when one
693
- // directive is duplicated elsewhere. Promoting an entire root file because
694
- // of one matching line can copy large provider/framework bodies into the
695
- // provider-neutral WORKSPACE.md operator region.
696
- const neutralSources = rootOperator.filter((source) => source.path === 'WORKSPACE.md').map((source) => source.path);
697
- const providerSources = rootOperator.filter((source) => source.path !== 'WORKSPACE.md' && !neutralSources.includes(source.path)).map((source) => source.path);
698
- const providerOutputs = providerSources.map((source) => providerContextOutput(projectPath, source));
704
+ // Provider startup files (CLAUDE.md, AGENTS.md, WARP.md, …) are bootstrap
705
+ // surfaces: AIWG rewrites them to a managed bootstrap that `@`-imports
706
+ // WORKSPACE.md. The operator content they carried therefore ports into the
707
+ // WORKSPACE.md operator block, which every provider bootstrap loads and
708
+ // which `aiwg use` and `aiwg regenerate` preserve byte-for-byte. Routing
709
+ // it to `.aiwg/context/providers/` instead left it linked but never
710
+ // loaded, and a later `aiwg use` dropped even the link (#2558).
711
+ const neutralSources = rootOperator.map((source) => source.path);
712
+ const providerSources = [];
713
+ const providerOutputs = [];
699
714
  const workspaceExists = sources.some((source) => source.path === 'WORKSPACE.md');
715
+ // Report volume and destination per source. A substantial provider-named
716
+ // body is still surfaced for review (#2537): it now lands where every
717
+ // provider reads it, so anything genuinely provider-only should be moved to
718
+ // `.aiwg/context/providers/` by the operator after migration.
719
+ const routing = rootOperator.map((source) => ({
720
+ source: source.path,
721
+ operatorBytes: Buffer.byteLength(source.operatorContent, 'utf8'),
722
+ destination: 'WORKSPACE.md',
723
+ scope: 'project-neutral',
724
+ provider: source.path === 'WORKSPACE.md' ? null : source.provider,
725
+ })).sort((a, b) => b.operatorBytes - a.operatorBytes);
726
+ const scopeReview = routing.filter((entry) => entry.source !== 'WORKSPACE.md' && entry.operatorBytes >= SCOPE_REVIEW_BYTES);
700
727
  return {
701
728
  version: 1,
702
729
  projectPath,
@@ -708,6 +735,8 @@ export async function auditWorkspaceContext(projectPath) {
708
735
  conflicts,
709
736
  sensitiveFindings,
710
737
  plan: {
738
+ routing,
739
+ scopeReview,
711
740
  neutralSources,
712
741
  providerSources,
713
742
  nestedSources: sources.filter((source) => source.scope === 'nested').map((source) => source.path),
@@ -719,34 +748,48 @@ export async function auditWorkspaceContext(projectPath) {
719
748
  function projectOutputPath(projectPath, absPath) {
720
749
  return path.relative(projectPath, absPath).replace(/\\/g, '/');
721
750
  }
722
- function providerContextOutput(projectPath, sourcePath) {
723
- return projectOutputPath(projectPath, projectAiwgPath(projectPath, 'context', 'providers', sourcePath.replace(/[^A-Za-z0-9.-]+/g, '-').replace(/^-+/, '')));
724
- }
751
+ const LEGACY_PROJECT_CONTEXT_PLACEHOLDER = [
752
+ '## Project Context',
753
+ '',
754
+ 'Add provider-neutral project conventions and links here.',
755
+ ].join('\n');
756
+ /**
757
+ * Operator content for the WORKSPACE.md operator block after migration.
758
+ *
759
+ * Existing WORKSPACE.md operator content is kept verbatim; the operator body
760
+ * of each provider startup file is appended once, verbatim, under an
761
+ * attributed heading. Tables, procedures, and code blocks survive intact —
762
+ * the earlier line-flattened directive list lost exactly the runbook content
763
+ * operators keep in CLAUDE.md (#2558). Re-running the migration is a no-op:
764
+ * a body that already appears in the block is not appended again.
765
+ */
725
766
  function neutralMigrationContent(audit) {
726
- const selected = audit.sources.filter((source) => audit.plan.neutralSources.includes(source.path));
727
- const seen = new Set();
728
- const lines = ['## Project Context', ''];
729
- for (const source of selected) {
730
- const unique = directives(source.operatorContent).filter((directive) => !seen.has(directive));
731
- for (const directive of unique)
732
- seen.add(directive);
733
- if (unique.length === 0)
767
+ const workspace = audit.sources.find((source) => source.path === 'WORKSPACE.md');
768
+ let base = workspace?.operatorContent.trim() ?? '';
769
+ if (base === PROJECT_CONTEXT_PLACEHOLDER || base === LEGACY_PROJECT_CONTEXT_PLACEHOLDER)
770
+ base = '';
771
+ const sections = [];
772
+ let accumulated = base;
773
+ for (const source of audit.sources) {
774
+ if (source.path === 'WORKSPACE.md' || !audit.plan.neutralSources.includes(source.path))
734
775
  continue;
735
- lines.push(`### From ${source.path}`, '', ...unique.map((directive) => `- ${directive}`), '');
776
+ const body = source.operatorContent.trim();
777
+ if (!body || accumulated.includes(body))
778
+ continue;
779
+ sections.push([
780
+ `### Migrated from ${source.path}`,
781
+ '',
782
+ `Source attribution: migrated from \`${source.path}\`; checksum \`${source.checksum}\`.`,
783
+ '',
784
+ body,
785
+ ].join('\n'));
786
+ accumulated = `${accumulated}\n${body}`;
736
787
  }
737
- if (lines.length === 2)
738
- lines.push('Add project conventions, local hook/context pointers, and links to deeper project documents here.');
739
- return lines.join('\n').trim();
740
- }
741
- function providerMigrationContent(source) {
742
- return [
743
- `# Provider-specific context from ${source.path}`,
744
- '',
745
- `Source attribution: migrated from \`${source.path}\`; checksum \`${source.checksum}\`.`,
746
- '',
747
- source.operatorContent.trim() || '(No operator-authored content remained after managed blocks were removed.)',
748
- '',
749
- ].join('\n');
788
+ const parts = [base || '## Project Context', ...sections];
789
+ if (!base && sections.length === 0) {
790
+ parts.push('Add project conventions, local hook/context pointers, and links to deeper project documents here.');
791
+ }
792
+ return parts.join('\n\n').trim();
750
793
  }
751
794
  async function configuredProviders(projectPath) {
752
795
  try {
@@ -761,17 +804,8 @@ async function configuredProviders(projectPath) {
761
804
  }
762
805
  async function stageMigrationWrites(projectPath, audit, options = {}) {
763
806
  const writes = new Map();
764
- for (const source of audit.sources.filter((item) => audit.plan.providerSources.includes(item.path))) {
765
- writes.set(providerContextOutput(projectPath, source.path), providerMigrationContent(source));
766
- }
767
- const providerFiles = [...new Set([
768
- ...audit.sources.filter((source) => source.path.replace(/\\/g, '/').includes('/context/providers/')).map((source) => source.path),
769
- ...writes.keys(),
770
- ])].sort();
771
- const existingWorkspace = audit.sources.find((source) => source.path === 'WORKSPACE.md');
772
- let workspaceOperatorContent = existingWorkspace?.managed
773
- ? existingWorkspace.operatorContent
774
- : neutralMigrationContent(audit);
807
+ const providerFiles = [...new Set(audit.sources.filter((source) => source.path.replace(/\\/g, '/').includes('/context/providers/')).map((source) => source.path))].sort();
808
+ let workspaceOperatorContent = neutralMigrationContent(audit);
775
809
  if (options.extractProject) {
776
810
  const extracted = await extractExistingProjectContext(projectPath);
777
811
  workspaceOperatorContent = mergeProjectExtraction(workspaceOperatorContent, extracted.content);
@@ -945,8 +979,35 @@ export async function rollbackWorkspaceContext(projectPath, requestedId) {
945
979
  await atomicWrite(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`);
946
980
  return { id, restored: manifest.files.map((file) => file.path) };
947
981
  }
982
+ /**
983
+ * Blank out fenced code blocks and inline code spans, preserving offsets and line
984
+ * structure, so link extraction sees only prose. Illustrative paths inside an
985
+ * example block are documentation, not context links the graph should resolve (#2536).
986
+ */
987
+ function maskCodeRegions(content) {
988
+ const lines = content.split('\n');
989
+ let fence = null;
990
+ const masked = lines.map((line) => {
991
+ const openOrClose = /^\s{0,3}(`{3,}|~{3,})(.*)$/.exec(line);
992
+ if (fence) {
993
+ // A closing fence uses the same character, is at least as long, and carries no info string.
994
+ if (openOrClose && openOrClose[1][0] === fence.marker && openOrClose[1].length >= fence.length && openOrClose[2].trim() === '') {
995
+ fence = null;
996
+ }
997
+ return ' '.repeat(line.length);
998
+ }
999
+ if (openOrClose) {
1000
+ fence = { marker: openOrClose[1][0], length: openOrClose[1].length };
1001
+ return ' '.repeat(line.length);
1002
+ }
1003
+ // Inline code spans: a run of N backticks closes on the next run of exactly N.
1004
+ return line.replace(/(`+)(?:[^`]|(?!\1)`)*?\1/g, (span) => ' '.repeat(span.length));
1005
+ });
1006
+ return masked.join('\n');
1007
+ }
948
1008
  function markdownLinks(content) {
949
- return [...content.matchAll(/\[[^\]]+\]\((\.\/?[^)#]+)(?:#[^)]+)?\)/g)].map((match) => match[1]);
1009
+ const prose = maskCodeRegions(content);
1010
+ return [...prose.matchAll(/\[[^\]]+\]\((\.\/?[^)#]+)(?:#[^)]+)?\)/g)].map((match) => match[1]);
950
1011
  }
951
1012
  export async function workspaceLinkedFiles(projectPath) {
952
1013
  const content = await readOptional(path.join(projectPath, 'WORKSPACE.md'));
@@ -1011,8 +1072,36 @@ export async function diagnoseWorkspaceContext(projectPath) {
1011
1072
  for (const conflict of audit.conflicts) {
1012
1073
  diagnostics.push({ severity: 'error', code: 'directive-conflict', message: `Conflicting duplicate directive group '${conflict.key}' appears in ${conflict.sources.map((source) => source.path).join(', ')}.` });
1013
1074
  }
1075
+ // One diagnostic per source pair, not one per duplicated line: a shared
1076
+ // pointer stub otherwise reports every line as a separate policy conflict
1077
+ // and degrades the workspace over content that agrees with itself (#2558).
1078
+ const duplicatesByPair = new Map();
1014
1079
  for (const overlap of audit.identical) {
1015
- diagnostics.push({ severity: 'warning', code: 'duplicate-directive', message: `Identical directive is duplicated across ${overlap.sources.join(', ')}.` });
1080
+ const key = overlap.sources.join(', ');
1081
+ duplicatesByPair.set(key, (duplicatesByPair.get(key) ?? 0) + overlap.directives.length);
1082
+ }
1083
+ const operatorBodies = new Map(audit.sources.map((source) => [source.path, source.operatorContent.trim()]));
1084
+ for (const [pair, count] of duplicatesByPair) {
1085
+ const paths = pair.split(', ');
1086
+ const bodies = paths.map((item) => operatorBodies.get(item) ?? '');
1087
+ const wholeFileDuplicate = bodies.every((body) => body.length > 0 && body === bodies[0]);
1088
+ diagnostics.push({
1089
+ severity: wholeFileDuplicate ? 'info' : 'warning',
1090
+ code: 'duplicate-directive',
1091
+ message: wholeFileDuplicate
1092
+ ? `${pair} carry identical operator content (${count} directive(s)); keep one copy or accept the shared stub.`
1093
+ : `${count} identical directive(s) duplicated across ${pair}.`,
1094
+ });
1095
+ }
1096
+ for (const source of audit.sources) {
1097
+ if (!source.path.replace(/\\/g, '/').includes('/context/providers/') || !source.operatorContent.trim())
1098
+ continue;
1099
+ diagnostics.push({
1100
+ severity: 'info',
1101
+ code: 'provider-context-not-loaded',
1102
+ message: `${source.path} is linked from WORKSPACE.md but no provider bootstrap auto-loads it; move its operator content into the WORKSPACE.md Project Context section (\`aiwg regenerate --existing-project\` ports startup files there) or keep it as reference only.`,
1103
+ path: source.path,
1104
+ });
1016
1105
  }
1017
1106
  for (const finding of audit.sensitiveFindings) {
1018
1107
  diagnostics.push({ severity: 'error', code: 'possible-secret', message: 'Possible credential value found in context; remove it.', path: finding.path });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aiwg/cli",
3
- "version": "2026.9.7",
3
+ "version": "2026.9.10",
4
4
  "description": "Lightweight AIWG CLI for signed, versioned web-backed resources.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -0,0 +1,61 @@
1
+ {
2
+ "contract": "fortemi.dataset-capability-validation/v1",
3
+ "revision": "1.0.1",
4
+ "wireContract": "fortemi.dataset-execution-capabilities/v1",
5
+ "wireSchemaVersion": "1.0.0",
6
+ "consumerIssue": 2242,
7
+ "authority": {
8
+ "repository": "Fortemi/fortemi-react",
9
+ "issue": "https://git.integrolabs.net/Fortemi/fortemi-react/issues/422",
10
+ "commit": "5625215762a8fd06db8fef4ccedd87f3c370ec31",
11
+ "mergedCommit": "35edcd9f8429fcd91a417021cbe4c427f900ed73",
12
+ "path": "packages/core/schemas/dataset-execution-capabilities/validation/1.0.1",
13
+ "manifestSha256": "20b59bb871c3afc9bb972896b2a2da718970c6fe7ef54b49000418f62a5701c0"
14
+ },
15
+ "files": [
16
+ {
17
+ "path": "capability.schema.json",
18
+ "authorityPath": "schema.json",
19
+ "sha256": "a4e9b427da669a02e83db00d39f48c2e106770d6cd2ec1b717938c57793b754c"
20
+ },
21
+ {
22
+ "path": "../../../../test/fixtures/dataset/fortemi-capability/negotiation-vectors.json",
23
+ "authorityPath": "negotiation-vectors.json",
24
+ "sha256": "2793990a842d5969f1f64c90170ab97ca4ffc4b93d1302a2f358fae54631a6dc"
25
+ },
26
+ {
27
+ "path": "../../../../test/fixtures/dataset/fortemi-capability/wire-vectors.json",
28
+ "authorityPath": "wire-vectors.json",
29
+ "sha256": "c52d32d0b1d0d312cf8b242f11fc116c47d104a74112cbd5d0019e6d39df9744"
30
+ }
31
+ ],
32
+ "consumer": {
33
+ "repository": "roctinam/aiwg",
34
+ "implementation": "src/dataset/fortemi-capability.ts",
35
+ "binding": "src/dataset/fortemi-dataset-execution.ts",
36
+ "test": "test/unit/dataset/fortemi-capability.test.ts",
37
+ "boundary": "Independent AIWG implementation of the wire rules. AIWG never imports the producer's validator; agreement is established by the shared authority vectors."
38
+ },
39
+ "observedDescriptor": {
40
+ "source": "Fortemi/fortemi mcp-server/lib/dataset-execution.js buildDatasetExecutionDescriptor",
41
+ "commit": "a701847db779a0a3adfa36a09dfa1055a10a0a0a",
42
+ "fixture": "test/fixtures/dataset/fortemi-capability/server-descriptor.json",
43
+ "sha256": "66ee315912429645e97c77a96e3f5110fe9db5e6909ff3c39e8b6e22135d8b95",
44
+ "runtimeVersion": "2026.9.10",
45
+ "qualification": "Descriptor rendered from pinned producer source; not a released-artifact or live-session receipt."
46
+ },
47
+ "qualification": {
48
+ "status": "source-consumer-adoption",
49
+ "covers": [
50
+ "advertised capability descriptor validation before preview or execute",
51
+ "independent negotiation bound to the advertised descriptor",
52
+ "receipt capabilityDecision bound to that independent negotiation"
53
+ ],
54
+ "remaining": [
55
+ "clean installed and released consumer qualification",
56
+ "live Fortemi Server session evidence under roctinam/aiwg#2194 authorization"
57
+ ],
58
+ "maturity": "alpha",
59
+ "suiteAudit": "NO-GO"
60
+ }
61
+ }