@aiwg/cli 2026.9.12 → 2026.9.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/agentic/code/providers/capability-matrix.yaml +42 -0
- package/agentic/code/providers/model-capabilities.v1.json +119 -34
- package/agentic/code/providers/model-catalog.v1.json +259 -56
- package/dist/src/cli/handlers/sessions.js +348 -42
- package/dist/src/cli/handlers/use.js +20 -1
- package/dist/src/cli/scope-resolver.js +13 -0
- package/dist/src/cli/services/deployment-verification.js +33 -2
- package/dist/src/config/aiwg-config.js +39 -0
- package/dist/src/config/user-registry.js +9 -0
- package/dist/src/extensions/manifest.js +1 -0
- package/dist/src/models/model-capabilities.v1.json +119 -34
- package/dist/src/models/model-catalog.v1.json +259 -56
- package/dist/src/models/model-discovery.js +7 -0
- package/dist/src/models/provider-policy.js +1 -1
- package/dist/src/plugin/skill-command-translator.js +1 -0
- package/dist/src/providers/capability-matrix.yaml +42 -0
- package/dist/src/providers/grokbot-paths.js +103 -0
- package/dist/src/providers/provider-definitions.js +85 -1
- package/dist/src/providers/provider-inventory.js +1 -0
- package/dist/src/sessions/adapters/claude.js +127 -3
- package/dist/src/sessions/adapters/grokbot.js +49 -0
- package/dist/src/sessions/adapters/hermes.js +17 -9
- package/dist/src/sessions/adapters/opencode.js +12 -1
- package/dist/src/sessions/contracts.js +1 -1
- package/dist/src/sessions/export-plan.js +243 -0
- package/dist/src/sessions/fortemi-export-mapping.js +300 -0
- package/dist/src/sessions/fortemi-shard-blobs.js +78 -0
- package/dist/src/sessions/fortemi-shard-recovery.js +151 -0
- package/dist/src/sessions/index.js +6 -0
- package/dist/src/sessions/output-lineage.js +43 -0
- package/dist/src/sessions/workspace-discovery.js +1 -1
- package/dist/src/skills/deployer.js +17 -0
- package/dist/src/smiths/skillsmith/collision-detector.js +1 -0
- package/package.json +1 -1
- package/tools/agents/deploy-agents.mjs +3 -2
- package/tools/agents/providers/grokbot.mjs +285 -0
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { existsSync, mkdirSync, realpathSync, statSync, } from 'node:fs';
|
|
2
|
-
import {
|
|
1
|
+
import { existsSync, linkSync, mkdirSync, readFileSync, realpathSync, renameSync, statSync, unlinkSync, writeFileSync, } from 'node:fs';
|
|
2
|
+
import { randomUUID } from 'node:crypto';
|
|
3
|
+
import { basename, dirname, isAbsolute, join, resolve, } from 'node:path';
|
|
3
4
|
import { pathToFileURL } from 'node:url';
|
|
4
|
-
import { CLAUDE_ADAPTER_VERSION, ClaudeSessionAdapter, CODEX_ADAPTER_VERSION, CodexSessionAdapter, COPILOT_ADAPTER_VERSION, CopilotSessionAdapter, CURSOR_ADAPTER_VERSION, CursorSessionAdapter, FACTORY_ADAPTER_VERSION, FactorySessionAdapter, HERMES_ADAPTER_VERSION, HermesSessionAdapter, OPENCODE_ADAPTER_VERSION, OpenCodeSessionAdapter, OPENCLAW_ADAPTER_VERSION, OpenClawSessionAdapter, OPENHUMAN_ADAPTER_VERSION, OpenHumanSessionAdapter, PI_ADAPTER_VERSION, PiSessionAdapter, DEEPSEEK_HARNESS_ADAPTER_VERSION, DeepSeekHarnessSessionAdapter, OmpSessionAdapter, OMP_ADAPTER_VERSION, WARP_ADAPTER_VERSION, WarpSessionAdapter, DEVIN_DESKTOP_ADAPTER_VERSION, DevinDesktopSessionAdapter, CandidateExtractionService, GENERIC_ADAPTER_VERSION, GenericSessionInterchangeAdapter, IncrementalSessionImporter, ImportLeaseContentionError, FilesystemMemoryDestination, FilesystemPromotionDispositionCoordinator, MemoryPromotionGateway, SESSION_CONTRACT_VERSION, SESSION_PROVIDER_IDS, SessionContractError, SessionRepository, SessionSourceSchema, StructuralCandidateExtractor, resolveMemoryConsumerManifest, assertSessionProviderId, acquireImportLease, defaultDiscoveryManifestPath, discoverWorkspaceHistories, deriveSessionTimeline, importDiscoveryManifest, previewDiscoveryImport, publicDiscoveryManifest, readDiscoveryManifest, redactSourceLocator, sha256, parseTimelineGap, writeDiscoveryManifest, } from '../../sessions/index.js';
|
|
5
|
+
import { CLAUDE_ADAPTER_VERSION, ClaudeSessionAdapter, CODEX_ADAPTER_VERSION, CodexSessionAdapter, COPILOT_ADAPTER_VERSION, CopilotSessionAdapter, CURSOR_ADAPTER_VERSION, CursorSessionAdapter, FACTORY_ADAPTER_VERSION, FactorySessionAdapter, HERMES_ADAPTER_VERSION, HermesSessionAdapter, OPENCODE_ADAPTER_VERSION, OpenCodeSessionAdapter, OPENCLAW_ADAPTER_VERSION, OpenClawSessionAdapter, OPENHUMAN_ADAPTER_VERSION, OpenHumanSessionAdapter, GROKBOT_ADAPTER_VERSION, GrokbotSessionAdapter, PI_ADAPTER_VERSION, PiSessionAdapter, DEEPSEEK_HARNESS_ADAPTER_VERSION, DeepSeekHarnessSessionAdapter, OmpSessionAdapter, OMP_ADAPTER_VERSION, WARP_ADAPTER_VERSION, WarpSessionAdapter, DEVIN_DESKTOP_ADAPTER_VERSION, DevinDesktopSessionAdapter, CandidateExtractionService, GENERIC_ADAPTER_VERSION, GenericSessionInterchangeAdapter, IncrementalSessionImporter, ImportLeaseContentionError, FilesystemMemoryDestination, FilesystemPromotionDispositionCoordinator, MemoryPromotionGateway, SESSION_CONTRACT_VERSION, SESSION_PROVIDER_IDS, SessionContractError, SessionRepository, SessionSourceSchema, StructuralCandidateExtractor, resolveMemoryConsumerManifest, assertSessionProviderId, acquireImportLease, defaultDiscoveryManifestPath, discoverWorkspaceHistories, deriveSessionTimeline, importDiscoveryManifest, previewDiscoveryImport, publicDiscoveryManifest, readDiscoveryManifest, redactSourceLocator, sha256, parseTimelineGap, writeDiscoveryManifest, buildSessionExportPlan, reverifySessionExportPlan, buildSessionAiwgFortemiIndexExport, recoverAiwgFortemiIndexFromFullV1Shard, embedAttachmentsInFullV1Shard, recoverEmbeddedAttachments, SESSION_EXPORT_PLAN_SCHEMA_VERSION, } from '../../sessions/index.js';
|
|
5
6
|
const JSON_CONTRACT_VERSION = '1.0.0';
|
|
6
7
|
async function createLineMemoryPromotionDestination(projectRoot, manifestPath) {
|
|
7
8
|
const modulePath = resolve(dirname(manifestPath), 'commands', 'line-memory.mjs');
|
|
@@ -33,6 +34,10 @@ Commands:
|
|
|
33
34
|
show <session-id> Show a session with events and tags
|
|
34
35
|
search <query> --workspace <id> Search authorized normalized content
|
|
35
36
|
extract [session-id] --workspace <id> Extract structural candidates
|
|
37
|
+
export plan --workspace <id> --out <file> <session-id...> Select sessions into a reviewable plan
|
|
38
|
+
export build --plan <file> --out <dir> Build a Fortemi Knowledge Shard from a plan
|
|
39
|
+
export verify --input <shard> Validate a shard and its receipt
|
|
40
|
+
export unpack --input <shard> --out <dir> Recover a shard's records to disk
|
|
36
41
|
candidates [--state <state>] List the candidate review queue
|
|
37
42
|
review <id> <version> <state> Record an explicit review transition
|
|
38
43
|
promote <id> <version> Preview promotion; use --confirm to write
|
|
@@ -65,6 +70,9 @@ Options:
|
|
|
65
70
|
--workspace <id>, --tag <tag>, --limit <n>, --cursor <n>
|
|
66
71
|
--page-size <n> Extraction scan page size (default 250, maximum 500)
|
|
67
72
|
--max-documents <n> Explicit extraction safety limit; returns a partial receipt
|
|
73
|
+
--include-bytes Embed registered-output bytes in session export shards
|
|
74
|
+
--max-attachment-bytes <n> Maximum bytes per embedded output (default 67108864)
|
|
75
|
+
--force Permit replacing an existing export or recovery output
|
|
68
76
|
|
|
69
77
|
Search filters:
|
|
70
78
|
--date-from <rfc3339>, --date-to <rfc3339>
|
|
@@ -409,6 +417,7 @@ async function executeCommand(ctx, args) {
|
|
|
409
417
|
? preview(command, { items, count: items.length, durableMemoryWrites: 0, scan })
|
|
410
418
|
: ok(command, { items, count: items.length, durableMemoryWrites: 0, scan });
|
|
411
419
|
}
|
|
420
|
+
case 'export': return exportCommand(ctx, args, repository);
|
|
412
421
|
case 'candidates': {
|
|
413
422
|
const { workspaceId } = readAuthorizationContext(ctx, args, command, repository);
|
|
414
423
|
const state = candidateState(args.values.get('--state'));
|
|
@@ -647,7 +656,7 @@ async function importSource(ctx, args) {
|
|
|
647
656
|
if (provider !== 'generic' && provider !== 'claude' && provider !== 'codex'
|
|
648
657
|
&& provider !== 'copilot' && provider !== 'cursor' && provider !== 'factory'
|
|
649
658
|
&& provider !== 'hermes' && provider !== 'opencode' && provider !== 'openclaw'
|
|
650
|
-
&& provider !== 'openhuman' && provider !== 'pi' && provider !== 'omp' && provider !== 'deepseek-harness' && provider !== 'warp' && provider !== 'devin-desktop') {
|
|
659
|
+
&& provider !== 'openhuman' && provider !== 'grokbot' && provider !== 'pi' && provider !== 'omp' && provider !== 'deepseek-harness' && provider !== 'warp' && provider !== 'devin-desktop') {
|
|
651
660
|
throw new CliError('UNSUPPORTED_OPERATION', `session import is not implemented for ${provider}`, EXIT.unsupported);
|
|
652
661
|
}
|
|
653
662
|
const sourceId = requiredValue(args, '--source-id');
|
|
@@ -663,6 +672,7 @@ async function importSource(ctx, args) {
|
|
|
663
672
|
const isOpenCode = provider === 'opencode';
|
|
664
673
|
const isOpenClaw = provider === 'openclaw';
|
|
665
674
|
const isOpenHuman = provider === 'openhuman';
|
|
675
|
+
const isGrokbot = provider === 'grokbot';
|
|
666
676
|
const isPi = provider === 'pi';
|
|
667
677
|
const isOmp = provider === 'omp';
|
|
668
678
|
const isDsh = provider === 'deepseek-harness';
|
|
@@ -686,15 +696,17 @@ async function importSource(ctx, args) {
|
|
|
686
696
|
? new OpenClawSessionAdapter()
|
|
687
697
|
: isOpenHuman
|
|
688
698
|
? new OpenHumanSessionAdapter()
|
|
689
|
-
:
|
|
690
|
-
? new
|
|
691
|
-
:
|
|
692
|
-
? new
|
|
693
|
-
:
|
|
694
|
-
? new
|
|
695
|
-
:
|
|
699
|
+
: isGrokbot
|
|
700
|
+
? new GrokbotSessionAdapter()
|
|
701
|
+
: isPi
|
|
702
|
+
? new PiSessionAdapter()
|
|
703
|
+
: isWarp
|
|
704
|
+
? new WarpSessionAdapter()
|
|
705
|
+
: isDevinDesktop
|
|
706
|
+
? new DevinDesktopSessionAdapter()
|
|
707
|
+
: new GenericSessionInterchangeAdapter();
|
|
696
708
|
const locatorClass = isDsh ? 'deepseek-harness-session-v2-jsonl' : isOmp ? 'omp-session-v3-jsonl' : isClaude
|
|
697
|
-
? (input
|
|
709
|
+
? claudeLocatorClass(input)
|
|
698
710
|
: isCodex
|
|
699
711
|
? (input.endsWith('.app-server.jsonl') ? 'codex-app-server-jsonl' : 'codex-rollout-jsonl')
|
|
700
712
|
: isCopilot
|
|
@@ -711,13 +723,15 @@ async function importSource(ctx, args) {
|
|
|
711
723
|
? 'openclaw-consistent-snapshot-jsonl'
|
|
712
724
|
: isOpenHuman
|
|
713
725
|
? 'openhuman-enriched-jsonl'
|
|
714
|
-
:
|
|
715
|
-
? '
|
|
716
|
-
:
|
|
717
|
-
? '
|
|
718
|
-
:
|
|
719
|
-
? '
|
|
720
|
-
:
|
|
726
|
+
: isGrokbot
|
|
727
|
+
? 'manual-export'
|
|
728
|
+
: isPi
|
|
729
|
+
? 'pi-session-v3-jsonl'
|
|
730
|
+
: isWarp
|
|
731
|
+
? 'warp-markdown-export'
|
|
732
|
+
: isDevinDesktop
|
|
733
|
+
? 'devin-desktop-cascade-hook-jsonl'
|
|
734
|
+
: 'manual-export';
|
|
721
735
|
const selectedSource = {
|
|
722
736
|
provider, locator: input, locatorClass, sourceId,
|
|
723
737
|
authorizedScope: { workspaceId, allowedRoots: [dirname(input)] },
|
|
@@ -726,7 +740,7 @@ async function importSource(ctx, args) {
|
|
|
726
740
|
const source = SessionSourceSchema.parse({
|
|
727
741
|
contractVersion: SESSION_CONTRACT_VERSION, sourceId, provider,
|
|
728
742
|
providerProfile: isDsh ? 'native-session-v2-jsonl' : isOmp ? 'native-title-slot-v3' : isClaude
|
|
729
|
-
?
|
|
743
|
+
? claudeProviderProfile(locatorClass)
|
|
730
744
|
: isCodex
|
|
731
745
|
? 'app-server-v2-rollout-fallback'
|
|
732
746
|
: isCopilot
|
|
@@ -743,11 +757,13 @@ async function importSource(ctx, args) {
|
|
|
743
757
|
? 'schema-16-event-v3-consistent-snapshot'
|
|
744
758
|
: isOpenHuman
|
|
745
759
|
? 'schema-1-session-raw-enriched'
|
|
746
|
-
:
|
|
747
|
-
? 'manual-
|
|
748
|
-
:
|
|
749
|
-
? '
|
|
750
|
-
:
|
|
760
|
+
: isGrokbot
|
|
761
|
+
? 'manual-interchange'
|
|
762
|
+
: isWarp
|
|
763
|
+
? 'manual-lossy-markdown-export'
|
|
764
|
+
: isDevinDesktop
|
|
765
|
+
? 'opt-in-cascade-transcript-hook'
|
|
766
|
+
: 'manual-interchange',
|
|
751
767
|
locatorClass, redactedLocator: redactSourceLocator(input),
|
|
752
768
|
adapterVersion: isDsh ? DEEPSEEK_HARNESS_ADAPTER_VERSION : isOmp ? OMP_ADAPTER_VERSION : isClaude
|
|
753
769
|
? CLAUDE_ADAPTER_VERSION
|
|
@@ -767,13 +783,15 @@ async function importSource(ctx, args) {
|
|
|
767
783
|
? OPENCLAW_ADAPTER_VERSION
|
|
768
784
|
: isOpenHuman
|
|
769
785
|
? OPENHUMAN_ADAPTER_VERSION
|
|
770
|
-
:
|
|
771
|
-
?
|
|
772
|
-
:
|
|
773
|
-
?
|
|
774
|
-
:
|
|
786
|
+
: isGrokbot
|
|
787
|
+
? GROKBOT_ADAPTER_VERSION
|
|
788
|
+
: isWarp
|
|
789
|
+
? WARP_ADAPTER_VERSION
|
|
790
|
+
: isDevinDesktop
|
|
791
|
+
? DEVIN_DESKTOP_ADAPTER_VERSION
|
|
792
|
+
: GENERIC_ADAPTER_VERSION,
|
|
775
793
|
sourceSchemaVersion: probe.sourceSchemaVersion,
|
|
776
|
-
disposition: isWarp
|
|
794
|
+
disposition: isWarp || isGrokbot
|
|
777
795
|
? 'manual-only'
|
|
778
796
|
: isClaude || isCodex || isCopilot || isCursor || isFactory || isHermes
|
|
779
797
|
|| isOpenCode || isOpenClaw || isOpenHuman || isDevinDesktop || isOmp || isDsh || isPi
|
|
@@ -798,14 +816,16 @@ async function importSource(ctx, args) {
|
|
|
798
816
|
? { 'native.openclaw': {} }
|
|
799
817
|
: isOpenHuman
|
|
800
818
|
? { 'native.openhuman': {} }
|
|
801
|
-
:
|
|
802
|
-
? { 'native.
|
|
803
|
-
:
|
|
804
|
-
? { 'native.
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
819
|
+
: isGrokbot
|
|
820
|
+
? { 'native.grokbot': {} }
|
|
821
|
+
: isWarp
|
|
822
|
+
? { 'native.warp': {} }
|
|
823
|
+
: isDevinDesktop
|
|
824
|
+
? { 'native.devin-desktop': {
|
|
825
|
+
product: 'Devin Desktop',
|
|
826
|
+
compatibilityProviderId: 'windsurf',
|
|
827
|
+
} }
|
|
828
|
+
: { 'native.generic': {} },
|
|
809
829
|
});
|
|
810
830
|
if (isDryRun(ctx, args)) {
|
|
811
831
|
return preview('import', { source, wouldInspect: true, wouldPersist: false });
|
|
@@ -837,6 +857,264 @@ async function importSource(ctx, args) {
|
|
|
837
857
|
await lease.release();
|
|
838
858
|
}
|
|
839
859
|
}
|
|
860
|
+
/**
|
|
861
|
+
* `aiwg sessions export {plan,build,verify,unpack}` (#2564). Sub-verb after
|
|
862
|
+
* `export`, matching the issue's proposed shape:
|
|
863
|
+
* export plan --workspace ID --out selection.json SESSION_ID...
|
|
864
|
+
* export build --plan selection.json --out DIRECTORY
|
|
865
|
+
* export verify --input evidence.shard
|
|
866
|
+
* export unpack --input evidence.shard --out DIRECTORY
|
|
867
|
+
*/
|
|
868
|
+
async function exportCommand(ctx, args, repository) {
|
|
869
|
+
const subVerb = requiredPositional(args, 0, 'export sub-command (plan|build|verify|unpack)');
|
|
870
|
+
const command = `export.${subVerb}`;
|
|
871
|
+
if (subVerb === 'plan')
|
|
872
|
+
return exportPlan(ctx, args, repository, command);
|
|
873
|
+
if (subVerb === 'build')
|
|
874
|
+
return exportBuild(ctx, args, repository, command);
|
|
875
|
+
if (subVerb === 'verify')
|
|
876
|
+
return exportVerify(ctx, args, command);
|
|
877
|
+
if (subVerb === 'unpack')
|
|
878
|
+
return exportUnpack(ctx, args, command);
|
|
879
|
+
throw new CliError('UNKNOWN_COMMAND', `unknown export sub-command: ${subVerb}`, EXIT.usage);
|
|
880
|
+
}
|
|
881
|
+
function exportPlan(ctx, args, repository, command) {
|
|
882
|
+
const workspaceId = normalizeWorkspaceId(ctx.cwd, requiredValue(args, '--workspace'));
|
|
883
|
+
const out = resolve(ctx.cwd, requiredValue(args, '--out'));
|
|
884
|
+
const flagSelection = args.values.get('--session')?.split(',').map((value) => value.trim()).filter(Boolean) ?? [];
|
|
885
|
+
const sessionIds = [...new Set([...flagSelection, ...args.positionals.slice(1)])];
|
|
886
|
+
const { plan } = buildSessionExportPlan(repository, { workspaceId, sessionIds, projectRoot: ctx.cwd });
|
|
887
|
+
mkdirSync(dirname(out), { recursive: true, mode: 0o700 });
|
|
888
|
+
writeFileSync(out, `${JSON.stringify(plan, null, 2)}\n`, { mode: 0o600 });
|
|
889
|
+
return ok(command, {
|
|
890
|
+
plan: out,
|
|
891
|
+
totals: plan.totals,
|
|
892
|
+
sessions: plan.sessions.map((entry) => entry.sessionId),
|
|
893
|
+
outputs: plan.outputs.length,
|
|
894
|
+
});
|
|
895
|
+
}
|
|
896
|
+
async function exportBuild(ctx, args, repository, command) {
|
|
897
|
+
const planPath = resolve(ctx.cwd, requiredValue(args, '--plan'));
|
|
898
|
+
const outDirectory = resolve(ctx.cwd, requiredValue(args, '--out'));
|
|
899
|
+
const shardName = args.values.get('--shard-name') ?? 'evidence.shard';
|
|
900
|
+
if (basename(shardName) !== shardName) {
|
|
901
|
+
throw new CliError('INVALID_ARGUMENT', '--shard-name must be a filename, not a path', EXIT.usage);
|
|
902
|
+
}
|
|
903
|
+
const finalPath = join(outDirectory, shardName);
|
|
904
|
+
const receiptPath = `${finalPath}.receipt.json`;
|
|
905
|
+
if (!args.flags.has('--force') && (existsSync(finalPath) || existsSync(receiptPath))) {
|
|
906
|
+
throw new CliError('OUTPUT_EXISTS', `refusing to overwrite an existing shard or receipt: ${finalPath} (use --force to replace)`, EXIT.usage);
|
|
907
|
+
}
|
|
908
|
+
let plan;
|
|
909
|
+
try {
|
|
910
|
+
plan = JSON.parse(readFileSync(planPath, 'utf8'));
|
|
911
|
+
}
|
|
912
|
+
catch {
|
|
913
|
+
throw new CliError('MALFORMED_SOURCE', `plan file is not readable JSON: ${planPath}`, EXIT.usage);
|
|
914
|
+
}
|
|
915
|
+
if (plan.schemaVersion !== SESSION_EXPORT_PLAN_SCHEMA_VERSION) {
|
|
916
|
+
throw new CliError('UNKNOWN_SCHEMA_MAJOR', `plan schemaVersion ${plan.schemaVersion} is not supported by this build (expected ${SESSION_EXPORT_PLAN_SCHEMA_VERSION})`, EXIT.contract);
|
|
917
|
+
}
|
|
918
|
+
// Recheck source changes before writing (#2564 acceptance criteria):
|
|
919
|
+
// re-select from the live repository and reject on any digest drift.
|
|
920
|
+
const { sessions, eventsBySessionId, catalogTagsBySessionId, outputBytesByRegistrationId, attachmentBytesByEventId, } = reverifySessionExportPlan(repository, plan, { projectRoot: ctx.cwd });
|
|
921
|
+
const includeBytes = args.flags.has('--include-bytes');
|
|
922
|
+
const outputs = plan.outputs.map((output) => ({
|
|
923
|
+
...output,
|
|
924
|
+
bytesEmbedded: includeBytes && outputBytesByRegistrationId.has(output.registrationId),
|
|
925
|
+
}));
|
|
926
|
+
const index = buildSessionAiwgFortemiIndexExport(plan.workspaceId, sessions, eventsBySessionId, outputs, catalogTagsBySessionId);
|
|
927
|
+
// aiwgFortemiIndexToKnowledgeShardWithReport targets the declared
|
|
928
|
+
// full-v1/2.0.0 archive contract and validates losslessness -- unlike the
|
|
929
|
+
// plain aiwgFortemiIndexToKnowledgeShard (core-v1), which silently embeds
|
|
930
|
+
// the whole input record as opaque note metadata regardless of whether it
|
|
931
|
+
// maps to any native component. The mapping in fortemi-export-mapping.ts
|
|
932
|
+
// was built specifically so every AIWG-specific field lands in a real
|
|
933
|
+
// full-v1 component (tags, provenance_events) rather than being smuggled
|
|
934
|
+
// through as an unmapped blob.
|
|
935
|
+
const { aiwgFortemiIndexToKnowledgeShardWithReport } = await import('@fortemi/core');
|
|
936
|
+
const result = await aiwgFortemiIndexToKnowledgeShardWithReport(index);
|
|
937
|
+
if (!result.success || !result.archive) {
|
|
938
|
+
throw new CliError('MALFORMED_SOURCE', `full-v1 shard conversion failed: ${JSON.stringify(result.losses)}`, EXIT.contract);
|
|
939
|
+
}
|
|
940
|
+
let bytes = result.archive;
|
|
941
|
+
let embeddedAttachmentCount = 0;
|
|
942
|
+
let embeddedAttachmentBytes = 0;
|
|
943
|
+
if (includeBytes) {
|
|
944
|
+
const maximum = Number(args.values.get('--max-attachment-bytes') ?? 64 * 1024 * 1024);
|
|
945
|
+
if (!Number.isSafeInteger(maximum) || maximum <= 0) {
|
|
946
|
+
throw new CliError('INVALID_ARGUMENT', '--max-attachment-bytes must be a positive integer', EXIT.usage);
|
|
947
|
+
}
|
|
948
|
+
const attachments = plan.outputs.map((output) => {
|
|
949
|
+
const content = outputBytesByRegistrationId.get(output.registrationId);
|
|
950
|
+
if (content.length > maximum) {
|
|
951
|
+
throw new CliError('SOURCE_TOO_LARGE', `registered output exceeds --max-attachment-bytes: ${output.outputLocator}`, EXIT.contract);
|
|
952
|
+
}
|
|
953
|
+
return {
|
|
954
|
+
recordId: `output_${output.registrationId}`,
|
|
955
|
+
attachmentId: randomUUID(),
|
|
956
|
+
filename: basename(output.outputLocator),
|
|
957
|
+
mimeType: output.mediaType,
|
|
958
|
+
bytes: content,
|
|
959
|
+
};
|
|
960
|
+
});
|
|
961
|
+
for (const attachment of plan.attachments ?? []) {
|
|
962
|
+
const content = attachmentBytesByEventId.get(attachment.eventId);
|
|
963
|
+
if (content.length > maximum) {
|
|
964
|
+
throw new CliError('SOURCE_TOO_LARGE', `session attachment exceeds --max-attachment-bytes: ${attachment.filename}`, EXIT.contract);
|
|
965
|
+
}
|
|
966
|
+
attachments.push({
|
|
967
|
+
recordId: attachment.eventId,
|
|
968
|
+
attachmentId: randomUUID(),
|
|
969
|
+
filename: basename(attachment.filename),
|
|
970
|
+
mimeType: attachment.mediaType,
|
|
971
|
+
bytes: content,
|
|
972
|
+
});
|
|
973
|
+
}
|
|
974
|
+
embeddedAttachmentCount = attachments.length;
|
|
975
|
+
embeddedAttachmentBytes = attachments.reduce((sum, attachment) => sum + attachment.bytes.length, 0);
|
|
976
|
+
bytes = await embedAttachmentsInFullV1Shard(bytes, attachments);
|
|
977
|
+
}
|
|
978
|
+
mkdirSync(outDirectory, { recursive: true, mode: 0o700 });
|
|
979
|
+
// Interrupted writes must never be mistaken for a completed export: write
|
|
980
|
+
// to a sibling temp file, then rename (atomic on the same filesystem), and
|
|
981
|
+
// only then write the receipt -- the receipt's existence is the completion
|
|
982
|
+
// signal, not the shard file's.
|
|
983
|
+
const tempPath = join(outDirectory, `.${shardName}.${randomUUID()}.tmp`);
|
|
984
|
+
writeFileSync(tempPath, bytes, { mode: 0o600 });
|
|
985
|
+
if (args.flags.has('--force'))
|
|
986
|
+
renameSync(tempPath, finalPath);
|
|
987
|
+
else {
|
|
988
|
+
try {
|
|
989
|
+
linkSync(tempPath, finalPath);
|
|
990
|
+
unlinkSync(tempPath);
|
|
991
|
+
}
|
|
992
|
+
catch (error) {
|
|
993
|
+
if (existsSync(tempPath))
|
|
994
|
+
unlinkSync(tempPath);
|
|
995
|
+
throw new CliError('OUTPUT_EXISTS', `refusing to overwrite an existing shard: ${finalPath}`, EXIT.usage, error);
|
|
996
|
+
}
|
|
997
|
+
}
|
|
998
|
+
const readBack = readFileSync(finalPath);
|
|
999
|
+
const shardDigest = sha256(readBack);
|
|
1000
|
+
if (readBack.length !== bytes.length) {
|
|
1001
|
+
throw new CliError('IMPORT_INTERRUPTED', 'shard readback size did not match the written archive', EXIT.storage);
|
|
1002
|
+
}
|
|
1003
|
+
const receipt = {
|
|
1004
|
+
schemaVersion: '1.0.0',
|
|
1005
|
+
generatedAt: new Date().toISOString(),
|
|
1006
|
+
workspaceId: plan.workspaceId,
|
|
1007
|
+
planGeneratedAt: plan.generatedAt,
|
|
1008
|
+
sessionIds: plan.sessions.map((entry) => entry.sessionId),
|
|
1009
|
+
totals: plan.totals,
|
|
1010
|
+
indexSchemaVersion: index.schema_version,
|
|
1011
|
+
archiveProfile: result.profile,
|
|
1012
|
+
archiveSchemaVersion: result.schema_version,
|
|
1013
|
+
lossless: result.lossless,
|
|
1014
|
+
losses: result.losses,
|
|
1015
|
+
fortemiReceipt: result.receipt,
|
|
1016
|
+
shardFile: shardName,
|
|
1017
|
+
shardDigest,
|
|
1018
|
+
shardBytes: readBack.length,
|
|
1019
|
+
embeddedAttachmentCount,
|
|
1020
|
+
embeddedAttachmentBytes,
|
|
1021
|
+
};
|
|
1022
|
+
writeFileSync(receiptPath, `${JSON.stringify(receipt, null, 2)}\n`, {
|
|
1023
|
+
mode: 0o600,
|
|
1024
|
+
flag: args.flags.has('--force') ? 'w' : 'wx',
|
|
1025
|
+
});
|
|
1026
|
+
return ok(command, { shard: finalPath, receipt: receiptPath, ...receipt });
|
|
1027
|
+
}
|
|
1028
|
+
async function exportVerify(ctx, args, command) {
|
|
1029
|
+
const input = resolve(ctx.cwd, requiredValue(args, '--input'));
|
|
1030
|
+
let bytes;
|
|
1031
|
+
try {
|
|
1032
|
+
bytes = readFileSync(input);
|
|
1033
|
+
}
|
|
1034
|
+
catch {
|
|
1035
|
+
throw new CliError('SOURCE_NOT_AUTHORIZED', `shard file is not readable: ${input}`, EXIT.usage);
|
|
1036
|
+
}
|
|
1037
|
+
let recovered;
|
|
1038
|
+
let embeddedAttachments;
|
|
1039
|
+
try {
|
|
1040
|
+
recovered = recoverAiwgFortemiIndexFromFullV1Shard(bytes);
|
|
1041
|
+
embeddedAttachments = recoverEmbeddedAttachments(bytes);
|
|
1042
|
+
}
|
|
1043
|
+
catch (error) {
|
|
1044
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1045
|
+
throw new CliError('MALFORMED_SOURCE', `shard failed validation: ${message}`, EXIT.contract);
|
|
1046
|
+
}
|
|
1047
|
+
const shardDigest = sha256(bytes);
|
|
1048
|
+
const receiptPath = `${input}.receipt.json`;
|
|
1049
|
+
let receiptMatches = null;
|
|
1050
|
+
if (existsSync(receiptPath)) {
|
|
1051
|
+
try {
|
|
1052
|
+
const receipt = JSON.parse(readFileSync(receiptPath, 'utf8'));
|
|
1053
|
+
receiptMatches = receipt.shardDigest === shardDigest && receipt.shardBytes === bytes.length;
|
|
1054
|
+
}
|
|
1055
|
+
catch {
|
|
1056
|
+
receiptMatches = false;
|
|
1057
|
+
}
|
|
1058
|
+
}
|
|
1059
|
+
return ok(command, {
|
|
1060
|
+
valid: true,
|
|
1061
|
+
schemaVersion: recovered.schema_version,
|
|
1062
|
+
itemCount: recovered.items.length,
|
|
1063
|
+
shardDigest,
|
|
1064
|
+
shardBytes: bytes.length,
|
|
1065
|
+
receiptChecked: existsSync(receiptPath),
|
|
1066
|
+
receiptMatches,
|
|
1067
|
+
embeddedAttachmentCount: embeddedAttachments.length,
|
|
1068
|
+
});
|
|
1069
|
+
}
|
|
1070
|
+
async function exportUnpack(ctx, args, command) {
|
|
1071
|
+
const input = resolve(ctx.cwd, requiredValue(args, '--input'));
|
|
1072
|
+
const outDirectory = resolve(ctx.cwd, requiredValue(args, '--out'));
|
|
1073
|
+
let bytes;
|
|
1074
|
+
try {
|
|
1075
|
+
bytes = readFileSync(input);
|
|
1076
|
+
}
|
|
1077
|
+
catch {
|
|
1078
|
+
throw new CliError('SOURCE_NOT_AUTHORIZED', `shard file is not readable: ${input}`, EXIT.usage);
|
|
1079
|
+
}
|
|
1080
|
+
let recovered;
|
|
1081
|
+
try {
|
|
1082
|
+
recovered = recoverAiwgFortemiIndexFromFullV1Shard(bytes);
|
|
1083
|
+
}
|
|
1084
|
+
catch (error) {
|
|
1085
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1086
|
+
throw new CliError('MALFORMED_SOURCE', `shard failed validation: ${message}`, EXIT.contract);
|
|
1087
|
+
}
|
|
1088
|
+
const attachments = recoverEmbeddedAttachments(bytes);
|
|
1089
|
+
mkdirSync(outDirectory, { recursive: true, mode: 0o700 });
|
|
1090
|
+
const finalPath = join(outDirectory, 'index.json');
|
|
1091
|
+
if (!args.flags.has('--force') && existsSync(finalPath)) {
|
|
1092
|
+
throw new CliError('OUTPUT_EXISTS', `refusing to overwrite recovered output: ${finalPath}`, EXIT.usage);
|
|
1093
|
+
}
|
|
1094
|
+
const attachmentPaths = attachments.map((attachment) => join(outDirectory, 'attachments', attachment.recordId, basename(attachment.filename)));
|
|
1095
|
+
if (!args.flags.has('--force')) {
|
|
1096
|
+
const conflict = attachmentPaths.find((attachmentPath) => existsSync(attachmentPath));
|
|
1097
|
+
if (conflict)
|
|
1098
|
+
throw new CliError('OUTPUT_EXISTS', `refusing to overwrite recovered attachment: ${conflict}`, EXIT.usage);
|
|
1099
|
+
}
|
|
1100
|
+
const tempPath = join(outDirectory, `.index.json.${randomUUID()}.tmp`);
|
|
1101
|
+
writeFileSync(tempPath, `${JSON.stringify(recovered, null, 2)}\n`, { mode: 0o600 });
|
|
1102
|
+
renameSync(tempPath, finalPath);
|
|
1103
|
+
for (const [index, attachment] of attachments.entries()) {
|
|
1104
|
+
const attachmentDirectory = join(outDirectory, 'attachments', attachment.recordId);
|
|
1105
|
+
mkdirSync(attachmentDirectory, { recursive: true, mode: 0o700 });
|
|
1106
|
+
writeFileSync(attachmentPaths[index], attachment.bytes, { mode: 0o600 });
|
|
1107
|
+
}
|
|
1108
|
+
return ok(command, {
|
|
1109
|
+
index: finalPath,
|
|
1110
|
+
schemaVersion: recovered.schema_version,
|
|
1111
|
+
itemCount: recovered.items.length,
|
|
1112
|
+
sessionRecordCount: recovered.items.filter((item) => item.type === 'aiwg.session').length,
|
|
1113
|
+
eventRecordCount: recovered.items.filter((item) => item.type === 'aiwg.session-event').length,
|
|
1114
|
+
recoveredAttachmentCount: attachments.length,
|
|
1115
|
+
recoveredAttachmentBytes: attachments.reduce((sum, attachment) => sum + attachment.bytes.length, 0),
|
|
1116
|
+
});
|
|
1117
|
+
}
|
|
840
1118
|
async function discoverWorkspace(ctx, args) {
|
|
841
1119
|
const workspace = resolve(ctx.cwd, requiredValue(args, '--workspace'));
|
|
842
1120
|
const manifest = await discoverWorkspaceHistories({
|
|
@@ -935,12 +1213,13 @@ function providerDisposition(provider) {
|
|
|
935
1213
|
return {
|
|
936
1214
|
provider, disposition: 'implemented', operationalState: 'available',
|
|
937
1215
|
supportedOperations: ['discover', 'inspect', 'stream'],
|
|
938
|
-
acquisitionModes: ['jsonl', 'hook'],
|
|
1216
|
+
acquisitionModes: ['jsonl', 'hook', 'manual-export'],
|
|
939
1217
|
reasonCode: null,
|
|
940
|
-
remediation: 'Authorize a Claude projects or hook root
|
|
1218
|
+
remediation: 'Authorize a Claude projects or hook root and import a JSONL file, '
|
|
1219
|
+
+ 'or import an explicitly selected Claude web/account conversations.json export.',
|
|
941
1220
|
evidence: {
|
|
942
1221
|
adapterVersion: CLAUDE_ADAPTER_VERSION,
|
|
943
|
-
verifiedAt: '2026-
|
|
1222
|
+
verifiedAt: '2026-09-15',
|
|
944
1223
|
documentation: 'https://code.claude.com/docs/en/sessions',
|
|
945
1224
|
},
|
|
946
1225
|
};
|
|
@@ -1082,6 +1361,20 @@ function providerDisposition(provider) {
|
|
|
1082
1361
|
},
|
|
1083
1362
|
};
|
|
1084
1363
|
}
|
|
1364
|
+
if (provider === 'grokbot') {
|
|
1365
|
+
return {
|
|
1366
|
+
provider, disposition: 'manual-only', operationalState: 'available',
|
|
1367
|
+
supportedOperations: ['inspect', 'stream'],
|
|
1368
|
+
acquisitionModes: ['manual-export'],
|
|
1369
|
+
reasonCode: 'MANUAL_SOURCE_SELECTION_REQUIRED',
|
|
1370
|
+
remediation: 'Select an authorized AIWG session interchange export; Grok Bot auto-discover is unsupported until a native locator exists.',
|
|
1371
|
+
evidence: {
|
|
1372
|
+
adapterVersion: GROKBOT_ADAPTER_VERSION,
|
|
1373
|
+
verifiedAt: '2026-09-15',
|
|
1374
|
+
documentation: 'docs/providers/grokbot-sessions.md',
|
|
1375
|
+
},
|
|
1376
|
+
};
|
|
1377
|
+
}
|
|
1085
1378
|
if (provider === 'warp') {
|
|
1086
1379
|
return {
|
|
1087
1380
|
provider, disposition: 'manual-only', operationalState: 'available',
|
|
@@ -1131,6 +1424,18 @@ function providerDisposition(provider) {
|
|
|
1131
1424
|
evidence: { adapterVersion: null, verifiedAt: '2026-07-26' },
|
|
1132
1425
|
};
|
|
1133
1426
|
}
|
|
1427
|
+
function claudeLocatorClass(input) {
|
|
1428
|
+
if (input.endsWith('.hooks.jsonl') || input.endsWith('.hook.jsonl'))
|
|
1429
|
+
return 'claude-hook-jsonl';
|
|
1430
|
+
if (input.endsWith('.json'))
|
|
1431
|
+
return 'claude-web-export-json';
|
|
1432
|
+
return 'claude-transcript-jsonl';
|
|
1433
|
+
}
|
|
1434
|
+
function claudeProviderProfile(locatorClass) {
|
|
1435
|
+
if (locatorClass === 'claude-web-export-json')
|
|
1436
|
+
return 'web-account-export-conversations-json';
|
|
1437
|
+
return 'documented-local-jsonl';
|
|
1438
|
+
}
|
|
1134
1439
|
function cursorLocatorClass(input) {
|
|
1135
1440
|
if (input.endsWith('.md') || input.endsWith('.markdown'))
|
|
1136
1441
|
return 'cursor-editor-markdown';
|
|
@@ -1182,6 +1487,7 @@ function parseArgs(argv) {
|
|
|
1182
1487
|
'--inactivity-threshold',
|
|
1183
1488
|
'--control-events',
|
|
1184
1489
|
'--session', '--status', '--actor', '--group-by',
|
|
1490
|
+
'--out', '--plan', '--input', '--shard-name', '--max-attachment-bytes',
|
|
1185
1491
|
]);
|
|
1186
1492
|
let command;
|
|
1187
1493
|
for (let index = 0; index < argv.length; index += 1) {
|
|
@@ -3102,6 +3102,7 @@ export class UseHandler {
|
|
|
3102
3102
|
version: manifest.version ?? (await getVersionInfo()).version,
|
|
3103
3103
|
source: 'bundled',
|
|
3104
3104
|
manifestHash: await hashManifest(manifestPath),
|
|
3105
|
+
asPrimary: Boolean(explicitAddonProvider),
|
|
3105
3106
|
});
|
|
3106
3107
|
await writeAiwgConfig(projectDir, updated);
|
|
3107
3108
|
config = updated;
|
|
@@ -3224,6 +3225,10 @@ export class UseHandler {
|
|
|
3224
3225
|
}
|
|
3225
3226
|
if (scope === 'user' && verbose) {
|
|
3226
3227
|
ui.dim(` --scope user: deploy targets mirror to home-rooted paths per ADR-4 §2`);
|
|
3228
|
+
if (process.env.AIWG_USER_REGISTRY_PATH?.trim()) {
|
|
3229
|
+
ui.warn('AIWG_USER_REGISTRY_PATH is set (test override active); user registry is not writing to default ~/.aiwg/installed.json'
|
|
3230
|
+
+ ` (active: ${process.env.AIWG_USER_REGISTRY_PATH})`);
|
|
3231
|
+
}
|
|
3227
3232
|
}
|
|
3228
3233
|
const filteredArgs = deployArgs.filter(a => a !== '--no-utils' && a !== '--no-project-local' && a !== '--ci-hooks-enabled' && a !== '--force' && a !== '--skip-conflicts' && a !== '--no-harness-agents');
|
|
3229
3234
|
const deployFilteredArgs = removeFlagWithOptionalValue(filteredArgs, '--harness-agents');
|
|
@@ -3324,6 +3329,15 @@ export class UseHandler {
|
|
|
3324
3329
|
message: `--scope user not supported for provider '${provider}' — see docs/customization/user-scope-deployment.md for the supported list`,
|
|
3325
3330
|
};
|
|
3326
3331
|
}
|
|
3332
|
+
if (provider === 'grokbot') {
|
|
3333
|
+
const { isGrokbotUserScopeConfigured, grokbotMissingRootRemediation } = await import('../../providers/grokbot-paths.js');
|
|
3334
|
+
if (!isGrokbotUserScopeConfigured()) {
|
|
3335
|
+
return {
|
|
3336
|
+
exitCode: 1,
|
|
3337
|
+
message: grokbotMissingRootRemediation(),
|
|
3338
|
+
};
|
|
3339
|
+
}
|
|
3340
|
+
}
|
|
3327
3341
|
}
|
|
3328
3342
|
// Pre-deployment collision check (skip in dry-run — nothing is written)
|
|
3329
3343
|
if (!dryRun) {
|
|
@@ -3853,7 +3867,12 @@ export class UseHandler {
|
|
|
3853
3867
|
commands: counts.commands,
|
|
3854
3868
|
skills: counts.skills,
|
|
3855
3869
|
rules: counts.rules,
|
|
3856
|
-
}, {
|
|
3870
|
+
}, {
|
|
3871
|
+
version: versionInfo.version,
|
|
3872
|
+
source: 'bundled',
|
|
3873
|
+
manifestHash: mHash,
|
|
3874
|
+
asPrimary: Boolean(explicitProvider),
|
|
3875
|
+
});
|
|
3857
3876
|
await writeAiwgConfig(projectDir, updatedConfig);
|
|
3858
3877
|
}
|
|
3859
3878
|
catch {
|
|
@@ -13,6 +13,7 @@ import { resolveOmpPaths } from '../providers/omp-paths.mjs';
|
|
|
13
13
|
import { homedir } from 'node:os';
|
|
14
14
|
import * as path from 'node:path';
|
|
15
15
|
import { resolveHermesHome, resolveHermesHomePath } from '../providers/hermes-home.js';
|
|
16
|
+
import { resolveGrokbotSkillsDir } from '../providers/grokbot-paths.js';
|
|
16
17
|
export const hermesHome = resolveHermesHome;
|
|
17
18
|
function piAgentDir() {
|
|
18
19
|
const configured = process.env.PI_CODING_AGENT_DIR;
|
|
@@ -216,6 +217,18 @@ export const USER_SCOPE_PATHS = {
|
|
|
216
217
|
rules: path.join(homedir(), '.openhuman', '.aiwg', 'rules'),
|
|
217
218
|
behaviors: '',
|
|
218
219
|
},
|
|
220
|
+
get grokbot() {
|
|
221
|
+
// Fail-closed: skills path is empty until AIWG_GROKBOT_SKILLS_DIR is set.
|
|
222
|
+
// Never invent ~/.grokbot. See adr-grokbot-provider-target / #205/#207.
|
|
223
|
+
const skills = resolveGrokbotSkillsDir() || '';
|
|
224
|
+
return {
|
|
225
|
+
agents: '',
|
|
226
|
+
skills,
|
|
227
|
+
commands: '',
|
|
228
|
+
rules: '',
|
|
229
|
+
behaviors: '',
|
|
230
|
+
};
|
|
231
|
+
},
|
|
219
232
|
factory: {
|
|
220
233
|
// #1164 — Verified against Factory docs (docs.factory.ai/cli/configuration/skills).
|
|
221
234
|
// Skills primary user-scope path is ~/.factory/skills/, NOT the
|
|
@@ -6,6 +6,7 @@ import { readUserRegistry } from '../../config/user-registry.js';
|
|
|
6
6
|
import { inspectInstallation } from '../../installation/manager.mjs';
|
|
7
7
|
import { getPackageRoot } from '../../channel/manager.mjs';
|
|
8
8
|
import { getProviderDefinition, normalizeProviderDefinitionId, resolveProviderPathValue, } from '../../providers/provider-definitions.js';
|
|
9
|
+
import { resolveGrokbotSkillsDir } from '../../providers/grokbot-paths.js';
|
|
9
10
|
import { diagnoseIntegratedProviderTransformationReceipt } from '../../providers/transformation-receipt-integration.js';
|
|
10
11
|
import { diagnoseWorkspaceContext, providerContextContract, } from '../../smiths/context-pipeline/workspace-context.js';
|
|
11
12
|
import { USER_SCOPE_PATHS } from '../scope-resolver.js';
|
|
@@ -31,6 +32,11 @@ const RESTART_NOTICES = {
|
|
|
31
32
|
action: 'Reload the Cursor workspace so it reloads agents and rules.',
|
|
32
33
|
reason: 'Cursor reads workspace agents and rules when the workspace opens.',
|
|
33
34
|
},
|
|
35
|
+
grokbot: {
|
|
36
|
+
policy: 'restart-required',
|
|
37
|
+
action: 'Start a new Grok Bot agent chat (or re-read skills) so deployed AIWG context and skills are visible.',
|
|
38
|
+
reason: 'Grok Bot skill/context reload behavior is not yet verified; AIWG does not claim live refresh.',
|
|
39
|
+
},
|
|
34
40
|
factory: {
|
|
35
41
|
policy: 'restart-required',
|
|
36
42
|
action: 'Restart the Factory droid runtime so it reloads deployed droids.',
|
|
@@ -362,7 +368,16 @@ export async function verifyProviderDeployment(options) {
|
|
|
362
368
|
const deploymentRoot = options.outputRoot ?? options.projectRoot;
|
|
363
369
|
const artifactPaths = options.scope === 'user'
|
|
364
370
|
? USER_SCOPE_PATHS[normalized] ?? definition.paths.artifacts
|
|
365
|
-
: definition.paths.artifacts;
|
|
371
|
+
: { ...definition.paths.artifacts };
|
|
372
|
+
// Grok Bot: project-scope definition keeps skills null (fail-closed sentinel).
|
|
373
|
+
// When the operator configured AIWG_GROKBOT_SKILLS_DIR, count that absolute
|
|
374
|
+
// root so verification matches the deployer (#210 PUW).
|
|
375
|
+
if (normalized === 'grokbot') {
|
|
376
|
+
const configuredSkills = resolveGrokbotSkillsDir();
|
|
377
|
+
if (configuredSkills) {
|
|
378
|
+
artifactPaths.skills = configuredSkills;
|
|
379
|
+
}
|
|
380
|
+
}
|
|
366
381
|
const writtenSince = options.invocationStartedAt
|
|
367
382
|
? Date.parse(options.invocationStartedAt)
|
|
368
383
|
: Number.NaN;
|
|
@@ -402,7 +417,23 @@ export async function verifyProviderDeployment(options) {
|
|
|
402
417
|
counts.skills += kernelCount;
|
|
403
418
|
const artifactTotal = Object.values(counts).reduce((sum, value) => sum + value, 0);
|
|
404
419
|
if (artifactTotal === 0) {
|
|
405
|
-
|
|
420
|
+
// Bridge-only providers (e.g. grokbot project scope without skills env)
|
|
421
|
+
// intentionally have null native artifact dirs and land AGENTS.md instead.
|
|
422
|
+
const bridgeName = definition.paths.configFile || definition.paths.contextFiles?.contextFile;
|
|
423
|
+
const bridgePath = bridgeName ? path.join(options.projectRoot, bridgeName) : '';
|
|
424
|
+
const bridgePresent = bridgePath ? await exists(bridgePath) : false;
|
|
425
|
+
const expectsNoNativeArtifacts = (!definition.paths.artifacts.agents
|
|
426
|
+
&& !definition.paths.artifacts.commands
|
|
427
|
+
&& !definition.paths.artifacts.skills
|
|
428
|
+
&& !definition.paths.artifacts.rules
|
|
429
|
+
&& !definition.paths.artifacts.behaviors
|
|
430
|
+
&& !definition.paths.kernelSkills);
|
|
431
|
+
if (expectsNoNativeArtifacts && bridgePresent) {
|
|
432
|
+
findings.push(finding(normalized, 'provider-bridge-only', 'info', `No native artifact directories for ${normalized}; discover-first bridge ${bridgeName} is present.`, 'Set AIWG_GROKBOT_SKILLS_DIR (absolute) before --scope user / --global when native skill copies are required.', { bridgePath, counts }));
|
|
433
|
+
}
|
|
434
|
+
else {
|
|
435
|
+
findings.push(finding(normalized, 'provider-artifacts-missing', 'blocking', `No deployed provider or kernel artifacts were found for ${normalized}.`, `Re-run aiwg use ${options.requestedBundles[0] ?? 'all'} --provider ${normalized}.`, { kernelPath, kernelCount, counts }));
|
|
436
|
+
}
|
|
406
437
|
}
|
|
407
438
|
}
|
|
408
439
|
findings.push(...await collectRegistryFindings(options, normalized, counts));
|