@aiwg/cli 2026.9.16 → 2026.9.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.
- package/agentic/code/providers/capability-matrix.yaml +38 -0
- package/dist/src/cli/scope-resolver.js +13 -0
- package/dist/src/cli/services/deployment-verification.js +57 -0
- package/dist/src/config/aiwg-config.js +2 -0
- package/dist/src/dataset/adapters.d.ts +1 -1
- package/dist/src/dataset/adapters.js +29 -7
- package/dist/src/dataset/conformance-types.d.ts +2 -1
- package/dist/src/dataset/conformance.js +10 -1
- package/dist/src/dataset/fortemi-live-qualification.d.ts +10 -0
- package/dist/src/dataset/fortemi-live-qualification.js +134 -0
- package/dist/src/providers/capability-matrix.yaml +38 -0
- package/dist/src/providers/grok-build-inspect.js +245 -0
- package/dist/src/providers/grok-build-paths.js +92 -0
- package/dist/src/providers/provider-definitions.js +87 -1
- package/dist/src/providers/provider-inventory.js +1 -0
- package/package.json +1 -1
- package/schemas/dataset/conformance-receipt.v1.schema.json +1 -1
- package/tools/agents/deploy-agents.mjs +4 -3
- package/tools/agents/providers/grok-build.mjs +153 -0
|
@@ -401,6 +401,44 @@ providers:
|
|
|
401
401
|
deploy_target: mixed
|
|
402
402
|
aggregated_output: false
|
|
403
403
|
|
|
404
|
+
grok-build:
|
|
405
|
+
display_name: Grok Build
|
|
406
|
+
status: experimental
|
|
407
|
+
# xAI Grok Build CLI/TUI. Distinct from Grok Bot (`grokbot`).
|
|
408
|
+
# Wave 1 (#2575): native kernel skills + AGENTS.md. Agents/rules writers deferred #2577.
|
|
409
|
+
daemon_tier: unsupported
|
|
410
|
+
daemon_pty_adapter: false
|
|
411
|
+
artifact_paths:
|
|
412
|
+
agents: "Indexed: aiwg discover / aiwg show agent (native .grok/agents writer deferred #2577)"
|
|
413
|
+
commands: null
|
|
414
|
+
skills: .grok/skills/
|
|
415
|
+
skills_cross_agent: .grok/.aiwg/skills/ # --copy-all opt-in mirror
|
|
416
|
+
rules: "Indexed: aiwg show rule + AGENTS.md / host .grok/rules (AIWG writer deferred #2577)"
|
|
417
|
+
behaviors: null
|
|
418
|
+
native_features:
|
|
419
|
+
cron: false
|
|
420
|
+
agent_teams: false
|
|
421
|
+
tasks: false
|
|
422
|
+
mcp: true
|
|
423
|
+
behaviors: false
|
|
424
|
+
mission_control: false
|
|
425
|
+
daemon: false
|
|
426
|
+
emulation:
|
|
427
|
+
cron: null
|
|
428
|
+
agent_teams: null
|
|
429
|
+
tasks: aiwg-mc
|
|
430
|
+
mcp: null
|
|
431
|
+
behaviors: aiwg-mc
|
|
432
|
+
mission_control: aiwg-mc
|
|
433
|
+
daemon: null
|
|
434
|
+
hook_wiring:
|
|
435
|
+
at_link_support: false
|
|
436
|
+
hook_file: null
|
|
437
|
+
fallback: full-inject
|
|
438
|
+
context_file: AGENTS.md
|
|
439
|
+
deploy_target: mixed
|
|
440
|
+
aggregated_output: false
|
|
441
|
+
|
|
404
442
|
hermes:
|
|
405
443
|
display_name: Hermes
|
|
406
444
|
status: stable
|
|
@@ -14,6 +14,7 @@ import { homedir } from 'node:os';
|
|
|
14
14
|
import * as path from 'node:path';
|
|
15
15
|
import { resolveHermesHome, resolveHermesHomePath } from '../providers/hermes-home.js';
|
|
16
16
|
import { resolveGrokbotSkillsDir } from '../providers/grokbot-paths.js';
|
|
17
|
+
import { resolveGrokHome } from '../providers/grok-build-paths.js';
|
|
17
18
|
export const hermesHome = resolveHermesHome;
|
|
18
19
|
function piAgentDir() {
|
|
19
20
|
const configured = process.env.PI_CODING_AGENT_DIR;
|
|
@@ -229,6 +230,18 @@ export const USER_SCOPE_PATHS = {
|
|
|
229
230
|
behaviors: '',
|
|
230
231
|
};
|
|
231
232
|
},
|
|
233
|
+
get 'grok-build'() {
|
|
234
|
+
// Skills mirror to $GROK_HOME/skills. Agents/rules writers deferred #2577 —
|
|
235
|
+
// leave those user paths empty so mirroring does not invent empty trees.
|
|
236
|
+
const home = resolveGrokHome();
|
|
237
|
+
return {
|
|
238
|
+
agents: '',
|
|
239
|
+
skills: home ? path.join(home, 'skills') : '',
|
|
240
|
+
commands: '',
|
|
241
|
+
rules: '',
|
|
242
|
+
behaviors: '',
|
|
243
|
+
};
|
|
244
|
+
},
|
|
232
245
|
factory: {
|
|
233
246
|
// #1164 — Verified against Factory docs (docs.factory.ai/cli/configuration/skills).
|
|
234
247
|
// Skills primary user-scope path is ~/.factory/skills/, NOT the
|
|
@@ -7,6 +7,8 @@ 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
9
|
import { resolveGrokbotSkillsDir } from '../../providers/grokbot-paths.js';
|
|
10
|
+
import { resolveGrokHome } from '../../providers/grok-build-paths.js';
|
|
11
|
+
import { runGrokInspect } from '../../providers/grok-build-inspect.js';
|
|
10
12
|
import { diagnoseIntegratedProviderTransformationReceipt } from '../../providers/transformation-receipt-integration.js';
|
|
11
13
|
import { diagnoseWorkspaceContext, providerContextContract, } from '../../smiths/context-pipeline/workspace-context.js';
|
|
12
14
|
import { USER_SCOPE_PATHS } from '../scope-resolver.js';
|
|
@@ -37,6 +39,11 @@ const RESTART_NOTICES = {
|
|
|
37
39
|
action: 'Start a new Grok Bot agent chat (or re-read skills) so deployed AIWG context and skills are visible.',
|
|
38
40
|
reason: 'Grok Bot skill/context reload behavior is not yet verified; AIWG does not claim live refresh.',
|
|
39
41
|
},
|
|
42
|
+
'grok-build': {
|
|
43
|
+
policy: 'restart-required',
|
|
44
|
+
action: 'Restart the Grok Build session so it reloads .grok skills and AGENTS.md.',
|
|
45
|
+
reason: 'Grok Build reload semantics are not yet verified for live refresh; treat deploys as restart-required.',
|
|
46
|
+
},
|
|
40
47
|
factory: {
|
|
41
48
|
policy: 'restart-required',
|
|
42
49
|
action: 'Restart the Factory droid runtime so it reloads deployed droids.',
|
|
@@ -435,6 +442,56 @@ export async function verifyProviderDeployment(options) {
|
|
|
435
442
|
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
443
|
}
|
|
437
444
|
}
|
|
445
|
+
if (normalized === 'grok-build') {
|
|
446
|
+
const expectedSkillNames = [];
|
|
447
|
+
// Prefer validating against kernel skills actually present under .grok/skills.
|
|
448
|
+
try {
|
|
449
|
+
const { readdirSync, existsSync: existsSyncFs } = await import('node:fs');
|
|
450
|
+
const skillsDir = path.resolve(options.projectRoot, '.grok', 'skills');
|
|
451
|
+
if (existsSyncFs(skillsDir)) {
|
|
452
|
+
for (const entry of readdirSync(skillsDir, { withFileTypes: true })) {
|
|
453
|
+
if (entry.isDirectory() && existsSyncFs(path.resolve(skillsDir, entry.name, 'SKILL.md'))) {
|
|
454
|
+
expectedSkillNames.push(entry.name);
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
catch {
|
|
460
|
+
// best-effort expected set
|
|
461
|
+
}
|
|
462
|
+
const inspect = runGrokInspect({
|
|
463
|
+
cwd: options.projectRoot,
|
|
464
|
+
expected: {
|
|
465
|
+
instructionPaths: ['AGENTS.md'],
|
|
466
|
+
skillNames: expectedSkillNames.slice(0, 32),
|
|
467
|
+
},
|
|
468
|
+
});
|
|
469
|
+
if (inspect.status === 'absent') {
|
|
470
|
+
findings.push(finding(normalized, 'grok-inspect-absent', 'advisory', 'Grok Build CLI (`grok`) is not on PATH; skipped `grok inspect`.', inspect.remediation, { grokHome: resolveGrokHome(), cwd: options.projectRoot }));
|
|
471
|
+
}
|
|
472
|
+
else if (inspect.status === 'failed') {
|
|
473
|
+
const mismatchSummary = (inspect.mismatches ?? [])
|
|
474
|
+
.slice(0, 8)
|
|
475
|
+
.map((item) => `${item.kind}:${item.expected}`)
|
|
476
|
+
.join('; ');
|
|
477
|
+
findings.push(finding(normalized, 'grok-inspect-failed', 'advisory', mismatchSummary
|
|
478
|
+
? `\`grok inspect\` did not confirm expected AIWG artifacts (${mismatchSummary}).`
|
|
479
|
+
: `\`grok inspect\` exited ${inspect.exitCode ?? 'unknown'}.`, 'Fix the Grok Build installation or redeploy, then re-run aiwg status --probe or aiwg use for grok-build.', {
|
|
480
|
+
exitCode: inspect.exitCode,
|
|
481
|
+
stderr: inspect.stderr.slice(0, 500),
|
|
482
|
+
cwd: inspect.cwd,
|
|
483
|
+
mismatches: inspect.mismatches?.slice(0, 12),
|
|
484
|
+
}));
|
|
485
|
+
}
|
|
486
|
+
else {
|
|
487
|
+
findings.push(finding(normalized, 'grok-inspect-ok', 'info', '`grok inspect` confirmed expected AIWG artifacts in the deployment target.', 'No action required; restart the Grok Build session if newly deployed skills are not visible.', {
|
|
488
|
+
binary: inspect.binary,
|
|
489
|
+
cwd: inspect.cwd,
|
|
490
|
+
parsedKeys: Object.keys(inspect.parsed).slice(0, 20),
|
|
491
|
+
checkedSkills: expectedSkillNames.slice(0, 32),
|
|
492
|
+
}));
|
|
493
|
+
}
|
|
494
|
+
}
|
|
438
495
|
}
|
|
439
496
|
findings.push(...await collectRegistryFindings(options, normalized, counts));
|
|
440
497
|
findings.push(...await collectProviderReceiptFindings(options, normalized));
|
|
@@ -173,6 +173,7 @@ export function resolveDelivery(delivery) {
|
|
|
173
173
|
* - hermes: MCP sidecar; rate-limit depends on upstream provider, operator
|
|
174
174
|
* should tune. Conservative 10 default.
|
|
175
175
|
* - grokbot: desktop multi-agent; conservative 4 until native evidence.
|
|
176
|
+
* - grok-build: experimental; conservative 4 until Wave 2/3 evidence.
|
|
176
177
|
* - unknown: conservative 4 default.
|
|
177
178
|
*/
|
|
178
179
|
export const PROVIDER_PARALLELISM_DEFAULTS = {
|
|
@@ -189,6 +190,7 @@ export const PROVIDER_PARALLELISM_DEFAULTS = {
|
|
|
189
190
|
hermes: { max_parallel_subagents: 10, max_parallel_ralph_loops: 3, max_parallel_mc_missions: 6 },
|
|
190
191
|
// Desktop multi-agent; conservative until native concurrency evidence exists.
|
|
191
192
|
grokbot: { max_parallel_subagents: 4, max_parallel_ralph_loops: 2, max_parallel_mc_missions: 4 },
|
|
193
|
+
'grok-build': { max_parallel_subagents: 4, max_parallel_ralph_loops: 2, max_parallel_mc_missions: 4 },
|
|
192
194
|
// Conservative defaults for remaining stable/experimental harnesses (#249).
|
|
193
195
|
openhuman: { max_parallel_subagents: 4, max_parallel_ralph_loops: 2, max_parallel_mc_missions: 4 },
|
|
194
196
|
omp: { max_parallel_subagents: 4, max_parallel_ralph_loops: 2, max_parallel_mc_missions: 4 },
|
|
@@ -45,7 +45,7 @@ declare abstract class LineAdapter<T extends FileConfig> extends LocalAdapter<T>
|
|
|
45
45
|
protected lineDiscovery(kind: string, config: T, request: AdapterRequest<T>, schema: Record<string, unknown>): Promise<AdapterDiscovery>;
|
|
46
46
|
protected lines(config: T, request: AdapterRequest<T> & {
|
|
47
47
|
checkpoint?: AdapterCheckpoint;
|
|
48
|
-
}, parse: (line: string, ordinal: number) => unknown): AsyncIterable<AdapterRecord>;
|
|
48
|
+
}, parse: (line: string, ordinal: number, maxDepth: number) => unknown): AsyncIterable<AdapterRecord>;
|
|
49
49
|
}
|
|
50
50
|
export declare class JsonlAdapter extends LineAdapter<FileConfig> {
|
|
51
51
|
#private;
|
|
@@ -108,6 +108,21 @@ function record(value, ordinal, sourceLocator) {
|
|
|
108
108
|
const serialized = typeof value === 'string' ? value : JSON.stringify(value);
|
|
109
109
|
return { logicalId: `sha256:${sha256Digest(`${sourceLocator}\0${ordinal}\0${serialized}`).value}`, ordinal, value, sourceLocator, contentDigest: sha256Digest(serialized) };
|
|
110
110
|
}
|
|
111
|
+
function valueDepth(value) {
|
|
112
|
+
let maximum = 0;
|
|
113
|
+
const pending = [{ value, depth: 0 }];
|
|
114
|
+
while (pending.length > 0) {
|
|
115
|
+
const current = pending.pop();
|
|
116
|
+
if (current.value === null || typeof current.value !== 'object')
|
|
117
|
+
continue;
|
|
118
|
+
const depth = current.depth + 1;
|
|
119
|
+
maximum = Math.max(maximum, depth);
|
|
120
|
+
const children = Array.isArray(current.value) ? current.value : Object.values(current.value);
|
|
121
|
+
for (const child of children)
|
|
122
|
+
pending.push({ value: child, depth });
|
|
123
|
+
}
|
|
124
|
+
return maximum;
|
|
125
|
+
}
|
|
111
126
|
function checkpoint(manifestValue, sourceIdentity, cursor) {
|
|
112
127
|
return {
|
|
113
128
|
contractVersion: DATASET_ADAPTER_CONTRACT_VERSION, kind: 'AdapterCheckpoint', adapter: { id: manifestValue.id, version: manifestValue.version },
|
|
@@ -227,7 +242,7 @@ class LineAdapter extends LocalAdapter {
|
|
|
227
242
|
if (Buffer.byteLength(line) > limits.maxRecordBytes || ordinal >= limits.maxRecords)
|
|
228
243
|
throw new AdapterFailure(diagnostic(operation, 'ADAPTER_RESOURCE_LIMIT', 'The record or record count exceeds configured limits.'));
|
|
229
244
|
if (line.trim() !== '')
|
|
230
|
-
yield record(parse(line, ordinal), ordinal, config.path);
|
|
245
|
+
yield record(parse(line, ordinal, limits.maxDepth), ordinal, config.path);
|
|
231
246
|
ordinal += 1;
|
|
232
247
|
}
|
|
233
248
|
}
|
|
@@ -243,12 +258,19 @@ export class JsonlAdapter extends LineAdapter {
|
|
|
243
258
|
valid(value) { return isFileConfig(value); }
|
|
244
259
|
discoverPath(config, request) { return this.lineDiscovery('jsonl', config, request, {}); }
|
|
245
260
|
records(config, request) {
|
|
246
|
-
return this.lines(config, request, (line, ordinal) => {
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
261
|
+
return this.lines(config, request, (line, ordinal, maxDepth) => {
|
|
262
|
+
try {
|
|
263
|
+
const value = JSON.parse(line);
|
|
264
|
+
if (valueDepth(value) > maxDepth)
|
|
265
|
+
throw new AdapterFailure(diagnostic(request.checkpoint ? 'read' : 'preview', 'ADAPTER_RESOURCE_LIMIT', `JSONL record at ordinal ${ordinal} exceeds the configured nesting limit.`));
|
|
266
|
+
return value;
|
|
267
|
+
}
|
|
268
|
+
catch (error) {
|
|
269
|
+
if (error instanceof AdapterFailure)
|
|
270
|
+
throw error;
|
|
271
|
+
throw new AdapterFailure(diagnostic(request.checkpoint ? 'read' : 'preview', 'ADAPTER_SCHEMA_DRIFT', `Malformed JSONL record at ordinal ${ordinal}.`));
|
|
272
|
+
}
|
|
273
|
+
});
|
|
252
274
|
}
|
|
253
275
|
}
|
|
254
276
|
function parseCsvLine(line) {
|
|
@@ -36,6 +36,7 @@ export interface DatasetConformanceManifest {
|
|
|
36
36
|
export interface DatasetConformanceBinding {
|
|
37
37
|
aiwgCommit: string;
|
|
38
38
|
fortemiCommit?: string;
|
|
39
|
+
fortemiServerCommit?: string;
|
|
39
40
|
packageDigests: Record<string, string>;
|
|
40
41
|
schemaDigests: Record<string, string>;
|
|
41
42
|
fixtureDigest: string;
|
|
@@ -75,7 +76,7 @@ export interface DatasetConformanceReceipt {
|
|
|
75
76
|
stableEligible: boolean;
|
|
76
77
|
};
|
|
77
78
|
}
|
|
78
|
-
export type DatasetConformanceDiagnosticCode = 'CONFORMANCE_MANIFEST_INVALID' | 'CONFORMANCE_CELL_DUPLICATE' | 'CONFORMANCE_REQUIRED_CELL_MISSING' | 'CONFORMANCE_FIXTURE_DIGEST_MISMATCH' | 'CONFORMANCE_RECEIPT_STALE' | 'CONFORMANCE_RECEIPT_UNVERIFIABLE' | 'CONFORMANCE_EVIDENCE_WEAKENED' | 'CONFORMANCE_MOCK_ONLY_STABLE' | 'CONFORMANCE_RESOURCE_ENVELOPE_MISSING' | 'CONFORMANCE_PENDING_STABLE_CELL' | 'CONFORMANCE_RESULT_FAILED' | 'CONFORMANCE_RESULT_DIGEST_MISMATCH' | 'CONFORMANCE_SENSITIVE_VALUE';
|
|
79
|
+
export type DatasetConformanceDiagnosticCode = 'CONFORMANCE_MANIFEST_INVALID' | 'CONFORMANCE_CELL_DUPLICATE' | 'CONFORMANCE_REQUIRED_CELL_MISSING' | 'CONFORMANCE_RESULT_DUPLICATE' | 'CONFORMANCE_RESULT_UNEXPECTED' | 'CONFORMANCE_FIXTURE_DIGEST_MISMATCH' | 'CONFORMANCE_RECEIPT_STALE' | 'CONFORMANCE_RECEIPT_UNVERIFIABLE' | 'CONFORMANCE_EVIDENCE_WEAKENED' | 'CONFORMANCE_MOCK_ONLY_STABLE' | 'CONFORMANCE_RESOURCE_ENVELOPE_MISSING' | 'CONFORMANCE_PENDING_STABLE_CELL' | 'CONFORMANCE_RESULT_FAILED' | 'CONFORMANCE_RESULT_DIGEST_MISMATCH' | 'CONFORMANCE_SENSITIVE_VALUE';
|
|
79
80
|
export interface DatasetConformanceDiagnostic {
|
|
80
81
|
code: DatasetConformanceDiagnosticCode;
|
|
81
82
|
path: string;
|
|
@@ -68,7 +68,16 @@ export function verifyConformanceReceipt(manifest, receipt) {
|
|
|
68
68
|
diagnostics.push({ code: 'CONFORMANCE_RECEIPT_UNVERIFIABLE', path: '/bindings/aiwgCommit', message: 'AIWG commit binding is invalid.' });
|
|
69
69
|
if (Object.keys(receipt.bindings.packageDigests).length === 0 || Object.keys(receipt.bindings.schemaDigests).length === 0)
|
|
70
70
|
diagnostics.push({ code: 'CONFORMANCE_RECEIPT_UNVERIFIABLE', path: '/bindings', message: 'Package and schema digest bindings are required.' });
|
|
71
|
-
const
|
|
71
|
+
const expectedIds = new Set(manifest.cells.map(cell => cell.id));
|
|
72
|
+
const resultById = new Map();
|
|
73
|
+
receipt.results.forEach((result, index) => {
|
|
74
|
+
if (resultById.has(result.cellId))
|
|
75
|
+
diagnostics.push({ code: 'CONFORMANCE_RESULT_DUPLICATE', path: `/results/${index}/cellId`, message: `Cell ${result.cellId} occurs more than once.` });
|
|
76
|
+
else
|
|
77
|
+
resultById.set(result.cellId, result);
|
|
78
|
+
if (!expectedIds.has(result.cellId))
|
|
79
|
+
diagnostics.push({ code: 'CONFORMANCE_RESULT_UNEXPECTED', path: `/results/${index}/cellId`, message: `Cell ${result.cellId} is not declared by the manifest.` });
|
|
80
|
+
});
|
|
72
81
|
manifest.cells.forEach(cell => {
|
|
73
82
|
const result = resultById.get(cell.id);
|
|
74
83
|
if (!result)
|
|
@@ -40,6 +40,16 @@ export interface FortemiDatasetLiveReceipt {
|
|
|
40
40
|
startedAt: string;
|
|
41
41
|
endedAt: string;
|
|
42
42
|
}
|
|
43
|
+
/**
|
|
44
|
+
* Verify the independently captured, mutating Fortemi dataset qualification.
|
|
45
|
+
* The caller supplies the separately persisted run receipt so a wrapper cannot
|
|
46
|
+
* claim a successful execution without the receipt's canonical digest checks.
|
|
47
|
+
*/
|
|
48
|
+
export declare function verifyFortemiDatasetExecutionQualification(input: {
|
|
49
|
+
qualification: unknown;
|
|
50
|
+
runReceipt: unknown;
|
|
51
|
+
expectedFortemiCommit: string;
|
|
52
|
+
}): string[];
|
|
43
53
|
/** Read-only discovery. It never invokes a Fortemi tool, even when the proposed contract is present. */
|
|
44
54
|
export declare function qualifyFortemiDatasetLivePreflight(input: {
|
|
45
55
|
client: McpClientLike;
|
|
@@ -3,6 +3,7 @@ import { link, mkdir, unlink, writeFile } from "node:fs/promises";
|
|
|
3
3
|
import { dirname } from "node:path";
|
|
4
4
|
import { FORTEMI_DATASET_EXECUTION_TOOL } from "./fortemi-dataset-execution.js";
|
|
5
5
|
import { endpointFingerprint, fortemiReceiptDigest, } from "../storage/fortemi-qualification-receipt.js";
|
|
6
|
+
import { verifyFortemiDatasetRunReceipt } from "./fortemi-run-receipt.js";
|
|
6
7
|
export const FORTEMI_DATASET_LIVE_CONTRACT = "aiwg.fortemi-dataset-live-qualification/v2";
|
|
7
8
|
const LEGACY_LIVE_CONTRACT = "aiwg.fortemi-dataset-live-qualification/v1";
|
|
8
9
|
export const FORTEMI_DATASET_CAPABILITIES_TOOL = "dataset_capabilities";
|
|
@@ -19,6 +20,18 @@ const REQUIRED_TOOLS = [FORTEMI_DATASET_EXECUTION_TOOL];
|
|
|
19
20
|
const REQUIRED_ACTIONS = ["capabilities", "preview", "execute", "status", "checkpoint", "cancel", "resume", "retry", "verify", "archive"];
|
|
20
21
|
const MAX_TOOL_COUNT = 256;
|
|
21
22
|
const MAX_SCHEMA_BYTES = 1_048_576;
|
|
23
|
+
const SENSITIVE = /(?:(?:password|passwd|api[-_]?key|authorization)\s*[:=]\s*[^\s"}]+|bearer\s+[a-z0-9._~+/-]+|-----BEGIN [A-Z ]+PRIVATE KEY-----)/iu;
|
|
24
|
+
const EXECUTION_QUALIFICATION_CONTRACT = "fortemi.lane-b.dataset-live-qualification.v1";
|
|
25
|
+
const EXECUTION_CHECKS = [
|
|
26
|
+
"published API identity and clean migrated destination",
|
|
27
|
+
"MCP initialize and capability discovery agree",
|
|
28
|
+
"schema-bound preview has no side effects",
|
|
29
|
+
"execute commits one synthetic record with valid redacted receipt",
|
|
30
|
+
"status, checkpoint, resume, and exact in-process replay are stable",
|
|
31
|
+
"fresh MCP process reconstructs byte-equivalent durable replay receipt",
|
|
32
|
+
"namespace-scoped archive cleanup is complete and idempotent",
|
|
33
|
+
];
|
|
34
|
+
const EXECUTION_BOUNDARY = "Synthetic single-record Community dataset execution against a private ephemeral PostgreSQL destination. No shared Fortemi, vLLM, Ollama, GPU, personal container, or persistent data is used.";
|
|
22
35
|
function record(value) {
|
|
23
36
|
return value !== null && typeof value === "object" && !Array.isArray(value)
|
|
24
37
|
? value
|
|
@@ -28,6 +41,127 @@ function exactKeys(value, allowed) {
|
|
|
28
41
|
const keys = Object.keys(value);
|
|
29
42
|
return (keys.length === allowed.length && keys.every((key) => allowed.includes(key)));
|
|
30
43
|
}
|
|
44
|
+
/**
|
|
45
|
+
* Verify the independently captured, mutating Fortemi dataset qualification.
|
|
46
|
+
* The caller supplies the separately persisted run receipt so a wrapper cannot
|
|
47
|
+
* claim a successful execution without the receipt's canonical digest checks.
|
|
48
|
+
*/
|
|
49
|
+
export function verifyFortemiDatasetExecutionQualification(input) {
|
|
50
|
+
const errors = [];
|
|
51
|
+
const qualification = record(input.qualification);
|
|
52
|
+
const runReceipt = record(input.runReceipt);
|
|
53
|
+
if (!COMMIT.test(input.expectedFortemiCommit))
|
|
54
|
+
return ["CONFORMANCE_INVALID_FORTEMI_COMMIT"];
|
|
55
|
+
if (!qualification || !runReceipt)
|
|
56
|
+
return ["CONFORMANCE_FORTEMI_EXECUTION_EVIDENCE_INVALID"];
|
|
57
|
+
if (!exactKeys(qualification, [
|
|
58
|
+
"schemaVersion", "status", "boundedUnit", "source", "release",
|
|
59
|
+
"executableSha256", "checks", "mcpSessions", "boundary", "apiPid",
|
|
60
|
+
"health", "migrations", "descriptorSha256", "run", "finalDatabase",
|
|
61
|
+
"apiExit", "apiPidAbsent", "apiOutputBytes", "apiOutputSha256",
|
|
62
|
+
"scratchRemoved",
|
|
63
|
+
]))
|
|
64
|
+
errors.push("CONFORMANCE_FORTEMI_EXECUTION_EVIDENCE_SHAPE_INVALID");
|
|
65
|
+
const source = qualification.source;
|
|
66
|
+
const release = qualification.release;
|
|
67
|
+
const checks = qualification.checks;
|
|
68
|
+
const sessions = qualification.mcpSessions;
|
|
69
|
+
const health = record(qualification.health);
|
|
70
|
+
const run = record(qualification.run);
|
|
71
|
+
const archive = record(run?.archive);
|
|
72
|
+
const finalDatabase = record(qualification.finalDatabase);
|
|
73
|
+
const apiExit = record(qualification.apiExit);
|
|
74
|
+
const receiptBindings = record(runReceipt.bindings);
|
|
75
|
+
const receiptCounts = record(runReceipt.counts);
|
|
76
|
+
const receiptEnvelope = record(runReceipt.resourceEnvelope);
|
|
77
|
+
const receiptRedaction = record(runReceipt.redaction);
|
|
78
|
+
if (SENSITIVE.test(JSON.stringify(qualification)) || SENSITIVE.test(JSON.stringify(runReceipt)))
|
|
79
|
+
errors.push("CONFORMANCE_FORTEMI_EXECUTION_SENSITIVE_VALUE");
|
|
80
|
+
if (qualification.schemaVersion !== EXECUTION_QUALIFICATION_CONTRACT ||
|
|
81
|
+
qualification.status !== "PASS" ||
|
|
82
|
+
source !== input.expectedFortemiCommit ||
|
|
83
|
+
typeof release !== "string" ||
|
|
84
|
+
!/^v\d{4}\.\d+\.\d+$/u.test(release) ||
|
|
85
|
+
typeof qualification.boundedUnit !== "string" ||
|
|
86
|
+
!/^fortemi-local-test-1000-[0-9a-f-]+\.service$/u.test(qualification.boundedUnit) ||
|
|
87
|
+
typeof qualification.apiPid !== "number" ||
|
|
88
|
+
!Number.isInteger(qualification.apiPid) || qualification.apiPid < 1 ||
|
|
89
|
+
qualification.boundary !== EXECUTION_BOUNDARY ||
|
|
90
|
+
typeof qualification.executableSha256 !== "string" ||
|
|
91
|
+
!/^[0-9a-f]{64}$/u.test(qualification.executableSha256) ||
|
|
92
|
+
typeof qualification.descriptorSha256 !== "string" ||
|
|
93
|
+
!/^[0-9a-f]{64}$/u.test(qualification.descriptorSha256) ||
|
|
94
|
+
typeof qualification.apiOutputSha256 !== "string" ||
|
|
95
|
+
!/^[0-9a-f]{64}$/u.test(qualification.apiOutputSha256))
|
|
96
|
+
errors.push("CONFORMANCE_FORTEMI_EXECUTION_BINDING_INVALID");
|
|
97
|
+
if (!Array.isArray(checks) ||
|
|
98
|
+
checks.length !== EXECUTION_CHECKS.length ||
|
|
99
|
+
!EXECUTION_CHECKS.every((check, index) => checks[index] === check))
|
|
100
|
+
errors.push("CONFORMANCE_FORTEMI_EXECUTION_CHECKS_INCOMPLETE");
|
|
101
|
+
if (!Array.isArray(sessions) ||
|
|
102
|
+
sessions.length !== 2 ||
|
|
103
|
+
!sessions.every((value, index) => {
|
|
104
|
+
const session = record(value);
|
|
105
|
+
return session &&
|
|
106
|
+
exactKeys(session, ["label", "pid", "stderrBytes", "closed"]) &&
|
|
107
|
+
session.label === (index === 0 ? "initial" : "restart") &&
|
|
108
|
+
typeof session.pid === "number" && Number.isInteger(session.pid) && session.pid > 0 &&
|
|
109
|
+
typeof session.stderrBytes === "number" && Number.isInteger(session.stderrBytes) && session.stderrBytes >= 0 &&
|
|
110
|
+
session.stderrBytes <= 65_536 && session.closed === true;
|
|
111
|
+
}))
|
|
112
|
+
errors.push("CONFORMANCE_FORTEMI_EXECUTION_SESSION_INVALID");
|
|
113
|
+
if (!health || health.status !== "healthy" || health.git_sha !== source ||
|
|
114
|
+
health.version !== String(release).slice(1) ||
|
|
115
|
+
record(health.lifecycle)?.ready !== true ||
|
|
116
|
+
typeof qualification.migrations !== "number" ||
|
|
117
|
+
!Number.isInteger(qualification.migrations) || qualification.migrations < 1)
|
|
118
|
+
errors.push("CONFORMANCE_FORTEMI_EXECUTION_RUNTIME_INVALID");
|
|
119
|
+
const receiptErrors = verifyFortemiDatasetRunReceipt(runReceipt);
|
|
120
|
+
if (receiptErrors.length)
|
|
121
|
+
errors.push("CONFORMANCE_FORTEMI_RUN_RECEIPT_INVALID", ...receiptErrors);
|
|
122
|
+
if (!run || !archive ||
|
|
123
|
+
!exactKeys(run, [
|
|
124
|
+
"runId", "namespaceId", "requestDigest", "receiptDigest", "state",
|
|
125
|
+
"verification", "checkpointSequence", "archive", "contentSha256",
|
|
126
|
+
"sourceContentRetained",
|
|
127
|
+
]) ||
|
|
128
|
+
!exactKeys(archive, [
|
|
129
|
+
"namespaceId", "archived", "alreadyArchived", "unresolved", "complete",
|
|
130
|
+
"reasonCodes",
|
|
131
|
+
]) ||
|
|
132
|
+
run.runId !== runReceipt.runId ||
|
|
133
|
+
run.namespaceId !== runReceipt.namespaceId ||
|
|
134
|
+
run.requestDigest !== runReceipt.requestDigest ||
|
|
135
|
+
run.receiptDigest !== runReceipt.receiptDigest ||
|
|
136
|
+
run.state !== runReceipt.state ||
|
|
137
|
+
run.verification !== "verified" ||
|
|
138
|
+
runReceipt.verification !== "verified" ||
|
|
139
|
+
run.checkpointSequence !== 1 ||
|
|
140
|
+
run.sourceContentRetained !== false ||
|
|
141
|
+
archive.namespaceId !== run.namespaceId || archive.complete !== true ||
|
|
142
|
+
archive.archived !== 1 || archive.alreadyArchived !== 0 ||
|
|
143
|
+
!Array.isArray(archive.unresolved) || archive.unresolved.length !== 0 ||
|
|
144
|
+
!Array.isArray(archive.reasonCodes) || archive.reasonCodes.length !== 0 ||
|
|
145
|
+
receiptBindings?.sourceRevision !== source ||
|
|
146
|
+
receiptCounts?.attempted !== 1 || receiptCounts?.committed !== 1 ||
|
|
147
|
+
receiptCounts?.rejected !== 0 || receiptEnvelope?.maxRecords !== 1 ||
|
|
148
|
+
receiptEnvelope?.allowOutboundNetwork !== false ||
|
|
149
|
+
receiptRedaction?.sourceContentIncluded !== false ||
|
|
150
|
+
receiptRedaction?.logicalIdentifiersIncluded !== false ||
|
|
151
|
+
receiptRedaction?.connectionDetailsIncluded !== false)
|
|
152
|
+
errors.push("CONFORMANCE_FORTEMI_EXECUTION_RECEIPT_MISMATCH");
|
|
153
|
+
if (!finalDatabase || finalDatabase.visibleNotes !== 0 ||
|
|
154
|
+
!exactKeys(finalDatabase, ["visibleNotes", "archivedNotes"]) ||
|
|
155
|
+
finalDatabase.archivedNotes !== 1 || !apiExit || apiExit.code !== 0 ||
|
|
156
|
+
!exactKeys(apiExit, ["code", "signal"]) ||
|
|
157
|
+
apiExit.signal !== null || qualification.apiPidAbsent !== true ||
|
|
158
|
+
qualification.scratchRemoved !== true ||
|
|
159
|
+
typeof qualification.apiOutputBytes !== "number" ||
|
|
160
|
+
!Number.isInteger(qualification.apiOutputBytes) ||
|
|
161
|
+
qualification.apiOutputBytes < 0 || qualification.apiOutputBytes > 262_144)
|
|
162
|
+
errors.push("CONFORMANCE_FORTEMI_EXECUTION_CLEANUP_INVALID");
|
|
163
|
+
return [...new Set(errors)];
|
|
164
|
+
}
|
|
31
165
|
function material(receipt) {
|
|
32
166
|
const { receiptDigest: _digest, ...rest } = receipt;
|
|
33
167
|
return rest;
|
|
@@ -401,6 +401,44 @@ providers:
|
|
|
401
401
|
deploy_target: mixed
|
|
402
402
|
aggregated_output: false
|
|
403
403
|
|
|
404
|
+
grok-build:
|
|
405
|
+
display_name: Grok Build
|
|
406
|
+
status: experimental
|
|
407
|
+
# xAI Grok Build CLI/TUI. Distinct from Grok Bot (`grokbot`).
|
|
408
|
+
# Wave 1 (#2575): native kernel skills + AGENTS.md. Agents/rules writers deferred #2577.
|
|
409
|
+
daemon_tier: unsupported
|
|
410
|
+
daemon_pty_adapter: false
|
|
411
|
+
artifact_paths:
|
|
412
|
+
agents: "Indexed: aiwg discover / aiwg show agent (native .grok/agents writer deferred #2577)"
|
|
413
|
+
commands: null
|
|
414
|
+
skills: .grok/skills/
|
|
415
|
+
skills_cross_agent: .grok/.aiwg/skills/ # --copy-all opt-in mirror
|
|
416
|
+
rules: "Indexed: aiwg show rule + AGENTS.md / host .grok/rules (AIWG writer deferred #2577)"
|
|
417
|
+
behaviors: null
|
|
418
|
+
native_features:
|
|
419
|
+
cron: false
|
|
420
|
+
agent_teams: false
|
|
421
|
+
tasks: false
|
|
422
|
+
mcp: true
|
|
423
|
+
behaviors: false
|
|
424
|
+
mission_control: false
|
|
425
|
+
daemon: false
|
|
426
|
+
emulation:
|
|
427
|
+
cron: null
|
|
428
|
+
agent_teams: null
|
|
429
|
+
tasks: aiwg-mc
|
|
430
|
+
mcp: null
|
|
431
|
+
behaviors: aiwg-mc
|
|
432
|
+
mission_control: aiwg-mc
|
|
433
|
+
daemon: null
|
|
434
|
+
hook_wiring:
|
|
435
|
+
at_link_support: false
|
|
436
|
+
hook_file: null
|
|
437
|
+
fallback: full-inject
|
|
438
|
+
context_file: AGENTS.md
|
|
439
|
+
deploy_target: mixed
|
|
440
|
+
aggregated_output: false
|
|
441
|
+
|
|
404
442
|
hermes:
|
|
405
443
|
display_name: Hermes
|
|
406
444
|
status: stable
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Grok Build verification via `grok inspect` when the binary is available.
|
|
3
|
+
* Presence-only remediation when absent — never invents inspect output.
|
|
4
|
+
*
|
|
5
|
+
* @issue #2575
|
|
6
|
+
*/
|
|
7
|
+
import { accessSync, constants as fsConstants } from 'node:fs';
|
|
8
|
+
import path from 'node:path';
|
|
9
|
+
import { spawnSync } from 'node:child_process';
|
|
10
|
+
export function grokExecutableCandidates(env, cwd, platform = process.platform) {
|
|
11
|
+
const pathApi = platform === 'win32' ? path.win32 : path.posix;
|
|
12
|
+
const pathDelimiter = platform === 'win32' ? ';' : ':';
|
|
13
|
+
const pathDirs = (env.PATH ?? env.Path ?? process.env.PATH ?? '')
|
|
14
|
+
.split(pathDelimiter)
|
|
15
|
+
// An empty PATH segment means the child cwd on POSIX and Windows.
|
|
16
|
+
.map((entry) => entry || '.');
|
|
17
|
+
const suffixes = platform === 'win32'
|
|
18
|
+
? [...(env.PATHEXT || env.Pathext || '.COM;.EXE;.BAT;.CMD')
|
|
19
|
+
.split(';')
|
|
20
|
+
.map((entry) => entry.trim())
|
|
21
|
+
.filter(Boolean), '']
|
|
22
|
+
: [''];
|
|
23
|
+
const candidates = new Set();
|
|
24
|
+
for (const dir of pathDirs) {
|
|
25
|
+
const absoluteDir = pathApi.isAbsolute(dir) ? dir : pathApi.resolve(cwd, dir);
|
|
26
|
+
for (const suffix of suffixes)
|
|
27
|
+
candidates.add(pathApi.join(absoluteDir, `grok${suffix}`));
|
|
28
|
+
}
|
|
29
|
+
return [...candidates];
|
|
30
|
+
}
|
|
31
|
+
function whichGrok(env, cwd) {
|
|
32
|
+
// Resolve from the supplied environment and deployment cwd without a shell.
|
|
33
|
+
for (const candidate of grokExecutableCandidates(env, cwd)) {
|
|
34
|
+
try {
|
|
35
|
+
accessSync(candidate, process.platform === 'win32' ? fsConstants.F_OK : fsConstants.X_OK);
|
|
36
|
+
return candidate;
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
// try next
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
function tryParseJson(text) {
|
|
45
|
+
const trimmed = text.trim();
|
|
46
|
+
if (!trimmed)
|
|
47
|
+
return null;
|
|
48
|
+
try {
|
|
49
|
+
const value = JSON.parse(trimmed);
|
|
50
|
+
return value && typeof value === 'object' && !Array.isArray(value)
|
|
51
|
+
? value
|
|
52
|
+
: null;
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
function asRecordArray(value) {
|
|
59
|
+
if (!Array.isArray(value))
|
|
60
|
+
return [];
|
|
61
|
+
return value.filter((entry) => Boolean(entry) && typeof entry === 'object' && !Array.isArray(entry));
|
|
62
|
+
}
|
|
63
|
+
function pathMentions(haystack, needle) {
|
|
64
|
+
const normalizedHay = haystack.replace(/\\/g, '/');
|
|
65
|
+
const normalizedNeedle = needle.replace(/\\/g, '/');
|
|
66
|
+
return normalizedHay.includes(normalizedNeedle);
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Validate a `grok inspect --json` report against artifacts AIWG intended to deploy.
|
|
70
|
+
* Shape follows xAI InspectReport (camelCase): projectInstructions, skills, agents, configSources.
|
|
71
|
+
*/
|
|
72
|
+
export function validateGrokInspectReport(parsed, expected = {}) {
|
|
73
|
+
const mismatches = [];
|
|
74
|
+
const instructions = asRecordArray(parsed.projectInstructions);
|
|
75
|
+
const skills = asRecordArray(parsed.skills);
|
|
76
|
+
const agents = asRecordArray(parsed.agents);
|
|
77
|
+
const configSources = parsed.configSources;
|
|
78
|
+
const layers = configSources && typeof configSources === 'object' && !Array.isArray(configSources)
|
|
79
|
+
? asRecordArray(configSources.layers)
|
|
80
|
+
: [];
|
|
81
|
+
const hasReportShape = Array.isArray(parsed.projectInstructions)
|
|
82
|
+
|| Array.isArray(parsed.skills)
|
|
83
|
+
|| Array.isArray(parsed.agents)
|
|
84
|
+
|| Boolean(configSources);
|
|
85
|
+
if (!hasReportShape) {
|
|
86
|
+
mismatches.push({
|
|
87
|
+
kind: 'shape',
|
|
88
|
+
expected: 'InspectReport fields (projectInstructions/skills/agents/configSources)',
|
|
89
|
+
detail: `parsed keys: ${Object.keys(parsed).slice(0, 20).join(',') || '(none)'}`,
|
|
90
|
+
});
|
|
91
|
+
return mismatches;
|
|
92
|
+
}
|
|
93
|
+
for (const instructionPath of expected.instructionPaths ?? []) {
|
|
94
|
+
const found = instructions.some((entry) => typeof entry.path === 'string' && pathMentions(entry.path, instructionPath));
|
|
95
|
+
if (!found) {
|
|
96
|
+
mismatches.push({
|
|
97
|
+
kind: 'instructions',
|
|
98
|
+
expected: instructionPath,
|
|
99
|
+
detail: `not listed in projectInstructions (${instructions.length} entries)`,
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
for (const skillName of expected.skillNames ?? []) {
|
|
104
|
+
const found = skills.some((entry) => (typeof entry.name === 'string' && entry.name === skillName)
|
|
105
|
+
|| (typeof entry.source === 'object' && entry.source !== null
|
|
106
|
+
&& typeof entry.source.path === 'string'
|
|
107
|
+
&& pathMentions(String(entry.source.path), skillName)));
|
|
108
|
+
if (!found) {
|
|
109
|
+
mismatches.push({
|
|
110
|
+
kind: 'skills',
|
|
111
|
+
expected: skillName,
|
|
112
|
+
detail: `not listed in skills (${skills.length} entries)`,
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
for (const agentName of expected.agentNames ?? []) {
|
|
117
|
+
const found = agents.some((entry) => typeof entry.name === 'string' && entry.name === agentName);
|
|
118
|
+
if (!found) {
|
|
119
|
+
mismatches.push({
|
|
120
|
+
kind: 'agents',
|
|
121
|
+
expected: agentName,
|
|
122
|
+
detail: `not listed in agents (${agents.length} entries)`,
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
for (const substring of expected.configPathSubstrings ?? []) {
|
|
127
|
+
const found = layers.some((entry) => typeof entry.path === 'string' && pathMentions(entry.path, substring));
|
|
128
|
+
if (!found) {
|
|
129
|
+
mismatches.push({
|
|
130
|
+
kind: 'config',
|
|
131
|
+
expected: substring,
|
|
132
|
+
detail: `not listed in configSources.layers (${layers.length} entries)`,
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
return mismatches;
|
|
137
|
+
}
|
|
138
|
+
const ABSENT_REMEDIATION = 'Install the Grok Build CLI so `grok` is on PATH, then re-run verification. '
|
|
139
|
+
+ 'Until then AIWG only confirms provider registration and on-disk .grok paths — it does not invent inspect output.';
|
|
140
|
+
function isRunOptions(value) {
|
|
141
|
+
if (!value || typeof value !== 'object')
|
|
142
|
+
return false;
|
|
143
|
+
const keys = Object.keys(value);
|
|
144
|
+
if (keys.length === 0)
|
|
145
|
+
return false;
|
|
146
|
+
return keys.every((key) => key === 'env' || key === 'cwd' || key === 'expected');
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Run `grok inspect` (prefer `--json` when the CLI accepts it).
|
|
150
|
+
* Accepts either a ProcessEnv (legacy) or an options bag with env/cwd/expected.
|
|
151
|
+
*/
|
|
152
|
+
export function runGrokInspect(envOrOptions = process.env) {
|
|
153
|
+
const options = isRunOptions(envOrOptions)
|
|
154
|
+
? envOrOptions
|
|
155
|
+
: { env: envOrOptions };
|
|
156
|
+
const env = options.env ?? process.env;
|
|
157
|
+
const cwd = options.cwd ?? process.cwd();
|
|
158
|
+
const binary = whichGrok(env, cwd);
|
|
159
|
+
if (!binary) {
|
|
160
|
+
return { status: 'absent', remediation: ABSENT_REMEDIATION };
|
|
161
|
+
}
|
|
162
|
+
// Prefer machine-readable inspect; fall back to plain inspect if --json is rejected.
|
|
163
|
+
// Use length/index checks — tuple `includes()` narrows incorrectly under TS (TS2345).
|
|
164
|
+
const argSets = [
|
|
165
|
+
['inspect', '--json'],
|
|
166
|
+
['inspect'],
|
|
167
|
+
];
|
|
168
|
+
for (let i = 0; i < argSets.length; i++) {
|
|
169
|
+
const args = argSets[i];
|
|
170
|
+
const wantsJson = args.length > 1 && args[1] === '--json';
|
|
171
|
+
const result = spawnSync(binary, [...args], {
|
|
172
|
+
encoding: 'utf8',
|
|
173
|
+
env,
|
|
174
|
+
cwd,
|
|
175
|
+
timeout: 15_000,
|
|
176
|
+
maxBuffer: 2_000_000,
|
|
177
|
+
// npm exposes Grok through a .cmd shim on Windows; Node requires a shell
|
|
178
|
+
// for .cmd/.bat launchers. Native .exe installs stay shell-free.
|
|
179
|
+
shell: process.platform === 'win32' && /\.(?:cmd|bat)$/i.test(binary),
|
|
180
|
+
});
|
|
181
|
+
if (result.error && result.error.code === 'ENOENT') {
|
|
182
|
+
return { status: 'absent', remediation: ABSENT_REMEDIATION };
|
|
183
|
+
}
|
|
184
|
+
const stdout = result.stdout || '';
|
|
185
|
+
const stderr = result.stderr || '';
|
|
186
|
+
if (result.status === 0) {
|
|
187
|
+
const parsed = tryParseJson(stdout);
|
|
188
|
+
if (!parsed) {
|
|
189
|
+
return {
|
|
190
|
+
status: 'failed',
|
|
191
|
+
binary,
|
|
192
|
+
exitCode: 0,
|
|
193
|
+
stderr,
|
|
194
|
+
stdout,
|
|
195
|
+
cwd,
|
|
196
|
+
mismatches: [{
|
|
197
|
+
kind: 'shape',
|
|
198
|
+
expected: 'JSON InspectReport',
|
|
199
|
+
detail: 'exit 0 but stdout was not a JSON object',
|
|
200
|
+
}],
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
const mismatches = validateGrokInspectReport(parsed, options.expected);
|
|
204
|
+
if (mismatches.length > 0) {
|
|
205
|
+
return {
|
|
206
|
+
status: 'failed',
|
|
207
|
+
binary,
|
|
208
|
+
exitCode: 0,
|
|
209
|
+
stderr,
|
|
210
|
+
stdout,
|
|
211
|
+
cwd,
|
|
212
|
+
mismatches,
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
return {
|
|
216
|
+
status: 'ok',
|
|
217
|
+
binary,
|
|
218
|
+
stdout,
|
|
219
|
+
parsed,
|
|
220
|
+
cwd,
|
|
221
|
+
mismatches: [],
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
if (wantsJson && /unknown|unrecognized|invalid/i.test(`${stderr}\n${stdout}`)) {
|
|
225
|
+
continue;
|
|
226
|
+
}
|
|
227
|
+
return {
|
|
228
|
+
status: 'failed',
|
|
229
|
+
binary,
|
|
230
|
+
exitCode: result.status,
|
|
231
|
+
stderr,
|
|
232
|
+
stdout,
|
|
233
|
+
cwd,
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
return {
|
|
237
|
+
status: 'failed',
|
|
238
|
+
binary,
|
|
239
|
+
exitCode: null,
|
|
240
|
+
stderr: 'grok inspect did not succeed',
|
|
241
|
+
stdout: '',
|
|
242
|
+
cwd,
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
//# sourceMappingURL=grok-build-inspect.js.map
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Grok Build home/path resolution — documented defaults with fail-closed overrides.
|
|
3
|
+
*
|
|
4
|
+
* Default `$GROK_HOME` is `~/.grok` (absolute after expand). Relative overrides,
|
|
5
|
+
* bare `~`, root-only paths, and null bytes are rejected.
|
|
6
|
+
*
|
|
7
|
+
* Distinct from Grok Bot (`grokbot` / `AIWG_GROKBOT_SKILLS_DIR`): never share
|
|
8
|
+
* roots or invent `~/.grokbot` / `.cursor` paths.
|
|
9
|
+
*
|
|
10
|
+
* @see https://docs.x.ai/build/settings
|
|
11
|
+
* @issue #2575
|
|
12
|
+
*/
|
|
13
|
+
import { homedir } from 'node:os';
|
|
14
|
+
import * as path from 'node:path';
|
|
15
|
+
export const GROK_HOME_ENV = 'GROK_HOME';
|
|
16
|
+
export function isGrokFilesystemRoot(resolved, pathFlavor = path) {
|
|
17
|
+
return pathFlavor.parse(resolved).root === resolved;
|
|
18
|
+
}
|
|
19
|
+
function expandHomePrefix(raw, userHome = homedir()) {
|
|
20
|
+
if (raw === '~')
|
|
21
|
+
return raw;
|
|
22
|
+
if (raw.startsWith('~/'))
|
|
23
|
+
return path.join(userHome, raw.slice(2));
|
|
24
|
+
return raw;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Resolve Grok Build home directory.
|
|
28
|
+
* When unset, defaults to `~/.grok` (expanded). Explicit env must be absolute or `~/…`.
|
|
29
|
+
*/
|
|
30
|
+
export function resolveGrokHomeResult(env = process.env, userHome = homedir()) {
|
|
31
|
+
const raw = (env[GROK_HOME_ENV] || '').trim();
|
|
32
|
+
const fromEnv = Boolean(raw);
|
|
33
|
+
const candidate = fromEnv ? raw : path.join(userHome, '.grok');
|
|
34
|
+
const expanded = expandHomePrefix(candidate, userHome);
|
|
35
|
+
if (expanded === '~' || expanded.startsWith('~')) {
|
|
36
|
+
return {
|
|
37
|
+
ok: false,
|
|
38
|
+
reason: 'not-absolute',
|
|
39
|
+
message: `${GROK_HOME_ENV} must be an absolute path or ~/… (got '${raw || '(default)'}'). Bare '~' is rejected.`,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
if (!path.isAbsolute(expanded)) {
|
|
43
|
+
return {
|
|
44
|
+
ok: false,
|
|
45
|
+
reason: 'not-absolute',
|
|
46
|
+
message: `${GROK_HOME_ENV} must be an absolute path (got '${raw}'). Relative overrides are rejected.`,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
if (raw.includes('\0') || expanded.includes('\0')) {
|
|
50
|
+
return {
|
|
51
|
+
ok: false,
|
|
52
|
+
reason: 'traversal',
|
|
53
|
+
message: `${GROK_HOME_ENV} contains an invalid path character.`,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
const resolved = path.resolve(expanded);
|
|
57
|
+
if (!resolved || isGrokFilesystemRoot(resolved)) {
|
|
58
|
+
return {
|
|
59
|
+
ok: false,
|
|
60
|
+
reason: 'root',
|
|
61
|
+
message: `${GROK_HOME_ENV} resolves to filesystem root; refuse to deploy there.`,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
return { ok: true, path: resolved, source: fromEnv ? 'env' : 'default' };
|
|
65
|
+
}
|
|
66
|
+
export function resolveGrokHome(env = process.env, userHome = homedir()) {
|
|
67
|
+
const result = resolveGrokHomeResult(env, userHome);
|
|
68
|
+
return result.ok ? result.path : null;
|
|
69
|
+
}
|
|
70
|
+
export function grokBuildProjectPaths() {
|
|
71
|
+
return {
|
|
72
|
+
skills: '.grok/skills',
|
|
73
|
+
agents: '.grok/agents',
|
|
74
|
+
rules: '.grok/rules',
|
|
75
|
+
hooks: '.grok/hooks',
|
|
76
|
+
config: '.grok/config.toml',
|
|
77
|
+
agentsMd: 'AGENTS.md',
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
export function grokBuildUserPaths(grokHome) {
|
|
81
|
+
return {
|
|
82
|
+
skills: path.join(grokHome, 'skills'),
|
|
83
|
+
agents: path.join(grokHome, 'agents'),
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
export function grokHomeRemediation(env = process.env) {
|
|
87
|
+
const result = resolveGrokHomeResult(env);
|
|
88
|
+
if (result.ok)
|
|
89
|
+
return `${GROK_HOME_ENV}=${result.path} (${result.source})`;
|
|
90
|
+
return result.message;
|
|
91
|
+
}
|
|
92
|
+
//# sourceMappingURL=grok-build-paths.js.map
|
|
@@ -21,6 +21,7 @@ const ProviderDefinitionSchema = z.object({
|
|
|
21
21
|
'deepseek-harness',
|
|
22
22
|
'factory',
|
|
23
23
|
'grokbot',
|
|
24
|
+
'grok-build',
|
|
24
25
|
'hermes',
|
|
25
26
|
'opencode',
|
|
26
27
|
'openclaw',
|
|
@@ -38,7 +39,7 @@ const ProviderDefinitionSchema = z.object({
|
|
|
38
39
|
upstream: z.object({
|
|
39
40
|
source: z.string().url(),
|
|
40
41
|
version: z.string().min(1),
|
|
41
|
-
revision: z.string().regex(/^[0-9a-f]{40}$/),
|
|
42
|
+
revision: z.string().regex(/^[0-9a-f]{40}$/).refine((value) => value !== '0'.repeat(40), { message: 'upstream.revision must be a real reviewed SHA, not all-zero' }),
|
|
42
43
|
runtime: z.string().min(1),
|
|
43
44
|
lastVerified: z.string().regex(/^\d{4}-\d{2}-\d{2}$/),
|
|
44
45
|
}).optional(),
|
|
@@ -143,6 +144,7 @@ export const PROVIDER_IDS = [
|
|
|
143
144
|
'deepseek-harness',
|
|
144
145
|
'factory',
|
|
145
146
|
'grokbot',
|
|
147
|
+
'grok-build',
|
|
146
148
|
'hermes',
|
|
147
149
|
'opencode',
|
|
148
150
|
'openclaw',
|
|
@@ -218,6 +220,24 @@ const CONTEXT_CONTRACTS = {
|
|
|
218
220
|
lastVerified: '2026-09-15',
|
|
219
221
|
},
|
|
220
222
|
},
|
|
223
|
+
'grok-build': {
|
|
224
|
+
// config.toml is configuration (MCP/plugins/permissions), not a startup/context file.
|
|
225
|
+
// Hierarchical instructions: AGENTS.md + .grok/rules/*.md, root-to-cwd, deeper wins.
|
|
226
|
+
startupFiles: ['AGENTS.md', '.grok/rules/*.md'],
|
|
227
|
+
precedence: [
|
|
228
|
+
'provider/system',
|
|
229
|
+
'global ~/.grok instruction sources',
|
|
230
|
+
'root-to-cwd AGENTS.md and .grok/rules/*.md (deeper files win on conflicts)',
|
|
231
|
+
'$GROK_HOME for user skills and user config',
|
|
232
|
+
],
|
|
233
|
+
loadMode: 'prose-directive', includeSyntax: null, configRegistration: null,
|
|
234
|
+
bootstrapTargets: ['AGENTS.md'], maxContextBytes: null, recommendedMaxLines: null, nestedContext: true, support: 'supported',
|
|
235
|
+
verification: {
|
|
236
|
+
method: 'xAI project-rules docs (AGENTS.md + .grok/rules hierarchical discovery); grok inspect when installed',
|
|
237
|
+
source: 'https://docs.x.ai/build/features/project-rules',
|
|
238
|
+
lastVerified: '2026-09-20',
|
|
239
|
+
},
|
|
240
|
+
},
|
|
221
241
|
hermes: {
|
|
222
242
|
startupFiles: ['.hermes.md', 'AGENTS.md'], precedence: ['provider/system', '.hermes.md', 'AGENTS.md'],
|
|
223
243
|
loadMode: 'prose-directive', includeSyntax: null, configRegistration: null,
|
|
@@ -632,6 +652,72 @@ const BUILT_IN_SEEDS = [
|
|
|
632
652
|
},
|
|
633
653
|
matrixRef: 'grokbot',
|
|
634
654
|
},
|
|
655
|
+
{
|
|
656
|
+
id: 'grok-build',
|
|
657
|
+
displayName: 'Grok Build',
|
|
658
|
+
aliases: [],
|
|
659
|
+
status: 'experimental',
|
|
660
|
+
builtIn: true,
|
|
661
|
+
upstream: {
|
|
662
|
+
source: 'https://github.com/xai-org/grok-build',
|
|
663
|
+
version: '1.0.38',
|
|
664
|
+
// Reviewed public tree (docs + inspect schema), not an all-zero placeholder.
|
|
665
|
+
revision: '4247f661689354b831191f11eeeac8424993fe3d',
|
|
666
|
+
runtime: 'Grok Build CLI',
|
|
667
|
+
lastVerified: '2026-09-20',
|
|
668
|
+
},
|
|
669
|
+
surfaces: {
|
|
670
|
+
primary: 'grok-build',
|
|
671
|
+
compatibility: [],
|
|
672
|
+
// Agents/rules native writers deferred #2577; host still discovers those dirs.
|
|
673
|
+
precedence: ['AGENTS.md', '.grok/skills/', '.grok/rules/ (host; AIWG writer deferred #2577)', '.grok/config.toml (config only)', '$GROK_HOME'],
|
|
674
|
+
related: [],
|
|
675
|
+
},
|
|
676
|
+
detection: { env: ['GROK_HOME'], process: ['grok'], capabilityId: 'grok-build' },
|
|
677
|
+
paths: {
|
|
678
|
+
deployTarget: 'mixed',
|
|
679
|
+
artifacts: {
|
|
680
|
+
// Wave 1 writes skills + AGENTS.md only; agents/rules indexed until #2577.
|
|
681
|
+
agents: null,
|
|
682
|
+
commands: null,
|
|
683
|
+
skills: '.grok/skills',
|
|
684
|
+
rules: null,
|
|
685
|
+
behaviors: null,
|
|
686
|
+
},
|
|
687
|
+
kernelSkills: '.grok/skills',
|
|
688
|
+
contextDiscovery: {
|
|
689
|
+
agents: '.grok/agents',
|
|
690
|
+
skills: '.grok/skills',
|
|
691
|
+
rules: '.grok/rules',
|
|
692
|
+
behaviors: null,
|
|
693
|
+
},
|
|
694
|
+
configFile: '.grok/config.toml',
|
|
695
|
+
contextFiles: { aiwgMd: true, agentsMd: true, claudeMdHook: false, hookFile: null, contextFile: 'AGENTS.md' },
|
|
696
|
+
},
|
|
697
|
+
smithPaths: {
|
|
698
|
+
agents: null,
|
|
699
|
+
commands: null,
|
|
700
|
+
skills: '.grok/skills',
|
|
701
|
+
rules: null,
|
|
702
|
+
fileExtension: '.md',
|
|
703
|
+
configFile: '.grok/config.toml',
|
|
704
|
+
aggregated: false,
|
|
705
|
+
},
|
|
706
|
+
skillNamespace: {
|
|
707
|
+
deploymentGroup: 'deep-recursion',
|
|
708
|
+
pathType: 'project',
|
|
709
|
+
skillsBaseDir: '.grok/skills',
|
|
710
|
+
subdirLayout: true,
|
|
711
|
+
},
|
|
712
|
+
adapters: {
|
|
713
|
+
agentFormat: 'agents-md',
|
|
714
|
+
hookBridge: null,
|
|
715
|
+
mcpInjection: null,
|
|
716
|
+
contextAggregation: 'agents-md',
|
|
717
|
+
ruleFormat: 'agents-md-section',
|
|
718
|
+
},
|
|
719
|
+
matrixRef: 'grok-build',
|
|
720
|
+
},
|
|
635
721
|
{
|
|
636
722
|
id: 'hermes',
|
|
637
723
|
aliases: [],
|
|
@@ -13,6 +13,7 @@ const PROVIDER_EXECUTABLES = {
|
|
|
13
13
|
'deepseek-harness': ['dsh'],
|
|
14
14
|
factory: ['droid'],
|
|
15
15
|
// Grok Bot has no verified CLI executable name; omit rather than invent.
|
|
16
|
+
'grok-build': ['grok'],
|
|
16
17
|
hermes: ['hermes'],
|
|
17
18
|
opencode: ['opencode'],
|
|
18
19
|
openclaw: ['openclaw'],
|
package/package.json
CHANGED
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
"$defs": {
|
|
16
16
|
"version": { "type": "string", "pattern": "^[0-9]+\\.[0-9]+\\.[0-9]+(?:[-+][0-9A-Za-z.-]+)?$" }, "digest": { "type": "string", "pattern": "^sha256:[0-9a-f]{64}$" }, "time": { "type": "string", "format": "date-time", "pattern": "Z$" },
|
|
17
17
|
"digestMap": { "type": "object", "minProperties": 1, "additionalProperties": { "$ref": "#/$defs/digest" } },
|
|
18
|
-
"bindings": { "type": "object", "additionalProperties": false, "required": ["aiwgCommit", "packageDigests", "schemaDigests", "fixtureDigest", "configurationDigest"], "properties": { "aiwgCommit": { "type": "string", "pattern": "^(?:[0-9a-f]{40}|sha256:[0-9a-f]{64})$" }, "fortemiCommit": { "type": "string", "pattern": "^[0-9a-f]{40}$" }, "packageDigests": { "$ref": "#/$defs/digestMap" }, "schemaDigests": { "$ref": "#/$defs/digestMap" }, "fixtureDigest": { "$ref": "#/$defs/digest" }, "descriptorDigest": { "$ref": "#/$defs/digest" }, "configurationDigest": { "$ref": "#/$defs/digest" } } },
|
|
18
|
+
"bindings": { "type": "object", "additionalProperties": false, "required": ["aiwgCommit", "packageDigests", "schemaDigests", "fixtureDigest", "configurationDigest"], "properties": { "aiwgCommit": { "type": "string", "pattern": "^(?:[0-9a-f]{40}|sha256:[0-9a-f]{64})$" }, "fortemiCommit": { "type": "string", "pattern": "^[0-9a-f]{40}$" }, "fortemiServerCommit": { "type": "string", "pattern": "^[0-9a-f]{40}$" }, "packageDigests": { "$ref": "#/$defs/digestMap" }, "schemaDigests": { "$ref": "#/$defs/digestMap" }, "fixtureDigest": { "$ref": "#/$defs/digest" }, "descriptorDigest": { "$ref": "#/$defs/digest" }, "configurationDigest": { "$ref": "#/$defs/digest" } } },
|
|
19
19
|
"evidence": { "type": "object", "additionalProperties": false, "required": ["kind", "reference", "digest"], "properties": { "kind": { "enum": ["fixture", "real-source", "cross-repo", "live-qualification"] }, "reference": { "type": "string", "minLength": 1 }, "digest": { "$ref": "#/$defs/digest" } } },
|
|
20
20
|
"result": { "type": "object", "additionalProperties": false, "required": ["cellId", "status", "evidence", "observed"], "properties": { "cellId": { "type": "string", "minLength": 1 }, "status": { "enum": ["passed", "failed", "pending"] }, "diagnostic": { "type": "string", "minLength": 1 }, "evidence": { "type": "array", "items": { "$ref": "#/$defs/evidence" } }, "observed": { "type": "object", "additionalProperties": false, "required": ["networkAttempts"], "properties": { "records": { "type": "integer", "minimum": 0 }, "bytes": { "type": "integer", "minimum": 0 }, "durationMs": { "type": "integer", "minimum": 0 }, "networkAttempts": { "type": "integer", "minimum": 0 } } } } }
|
|
21
21
|
}
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
* --rules-only Deploy only rules (skip agents)
|
|
21
21
|
* --dry-run Show what would be deployed without writing
|
|
22
22
|
* --force Overwrite existing files
|
|
23
|
-
* --provider <name> Target provider: antigravity (agy), claude (default), openai, codex, cursor, opencode, copilot, factory, grokbot, pi, omp, deepseek-harness (dsh), warp, devin, hermes, openhuman, or openclaw
|
|
23
|
+
* --provider <name> Target provider: antigravity (agy), claude (default), openai, codex, cursor, opencode, copilot, factory, grokbot, grok-build, pi, omp, deepseek-harness (dsh), warp, devin, hermes, openhuman, or openclaw
|
|
24
24
|
* --model <name> Override model for all tiers (blanket)
|
|
25
25
|
* --reasoning-model <name> Override model for reasoning tasks
|
|
26
26
|
* --coding-model <name> Override model for coding tasks
|
|
@@ -123,7 +123,7 @@ const PROVIDER_ALIASES = {
|
|
|
123
123
|
'dsh': 'deepseek-harness',
|
|
124
124
|
};
|
|
125
125
|
|
|
126
|
-
const AVAILABLE_PROVIDERS = ['antigravity', 'claude', 'factory', 'codex', 'opencode', 'copilot', 'cursor', 'pi', 'omp', 'deepseek-harness', 'warp', 'windsurf', 'hermes', 'openclaw', 'openhuman', 'grokbot'];
|
|
126
|
+
const AVAILABLE_PROVIDERS = ['antigravity', 'claude', 'factory', 'codex', 'opencode', 'copilot', 'cursor', 'pi', 'omp', 'deepseek-harness', 'warp', 'windsurf', 'hermes', 'openclaw', 'openhuman', 'grokbot', 'grok-build'];
|
|
127
127
|
|
|
128
128
|
const UNSUPPORTED_PROVIDER_HINTS = {
|
|
129
129
|
'devin-cli': [
|
|
@@ -651,7 +651,8 @@ Providers (all deploy agents, commands, skills, and rules):
|
|
|
651
651
|
Paths: AGENTS.md, .agents/skills/, .dsh/aiwg.cordis.patch.yml
|
|
652
652
|
devin - Devin Desktop (preferred; aliases: devin-desktop, windsurf)
|
|
653
653
|
Paths: .windsurf/agents/, .windsurf/workflows/, .windsurf/skills/, .windsurf/rules/
|
|
654
|
-
grokbot - Grok Bot (
|
|
654
|
+
grokbot - Grok Bot (AGENTS.md bridge; skills only with AIWG_GROKBOT_SKILLS_DIR)
|
|
655
|
+
grok-build - Grok Build (experimental; .grok paths + $GROK_HOME; no bare grok alias)
|
|
655
656
|
hermes - Hermes Agent (MCP-based integration)
|
|
656
657
|
Skills: $HERMES_HOME/skills/ (user-global; defaults to ~/.hermes/skills/) | Agents: AGENTS.md
|
|
657
658
|
Commands/Rules: served via MCP, not file-deployed
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Grok Build provider (experimental).
|
|
3
|
+
* Project skills under `.grok/skills`; user home via $GROK_HOME (default ~/.grok).
|
|
4
|
+
* Distinct from grokbot. No bare `grok` alias.
|
|
5
|
+
*
|
|
6
|
+
* Wave 1: native kernel skills + AGENTS.md bridge. Agents and rules are
|
|
7
|
+
* indexed/deferred until #2577 (Grok discovers `.grok/agents` and
|
|
8
|
+
* `.grok/rules`, but AIWG does not yet emit qualified writers for them).
|
|
9
|
+
*
|
|
10
|
+
* @issue #2575
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import path from 'node:path';
|
|
14
|
+
import os from 'node:os';
|
|
15
|
+
import {
|
|
16
|
+
createAgentsMdFromTemplate,
|
|
17
|
+
deploySkillsWithKernelRouting,
|
|
18
|
+
collectFrameworkArtifacts,
|
|
19
|
+
getAddonSkillDirs,
|
|
20
|
+
normalizeDeploymentMode,
|
|
21
|
+
resolveAiwgRoot,
|
|
22
|
+
} from './base.mjs';
|
|
23
|
+
|
|
24
|
+
export const name = 'grok-build';
|
|
25
|
+
export const aliases = [];
|
|
26
|
+
export const paths = {
|
|
27
|
+
// Agents/rules dirs exist in Grok Build, but Wave 1 does not write them.
|
|
28
|
+
agents: '',
|
|
29
|
+
skills: '.grok/skills',
|
|
30
|
+
rules: '',
|
|
31
|
+
hooks: '.grok/hooks',
|
|
32
|
+
config: '.grok/config.toml',
|
|
33
|
+
};
|
|
34
|
+
export const kernelSkillsPath = '.grok/skills';
|
|
35
|
+
/** Standard-tier opt-in mirror (`--copy-all`) lives under the project .aiwg tree. */
|
|
36
|
+
export const standardSkillsPath = '.grok/.aiwg/skills';
|
|
37
|
+
|
|
38
|
+
export const support = {
|
|
39
|
+
agents: 'indexed', // deferred native writer until #2577
|
|
40
|
+
commands: 'indexed', // discoverable through the AIWG index; no native command writer
|
|
41
|
+
skills: 'native',
|
|
42
|
+
rules: 'indexed', // deferred native writer until #2577; host still loads AGENTS.md + .grok/rules hierarchically
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
export const capabilities = {
|
|
46
|
+
skills: true,
|
|
47
|
+
rules: false, // indexed/deferred — do not claim native rule transforms yet
|
|
48
|
+
yamlFormat: false,
|
|
49
|
+
aggregatedOutput: false,
|
|
50
|
+
homeDirectoryDeploy: true,
|
|
51
|
+
parallelCommandAndSkillSurfaces: false,
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
const GROK_HOME_ENV = 'GROK_HOME';
|
|
55
|
+
|
|
56
|
+
export function resolveGrokHome(env = process.env, userHome = os.homedir()) {
|
|
57
|
+
const raw = (env[GROK_HOME_ENV] || '').trim();
|
|
58
|
+
const candidate = raw || path.join(userHome, '.grok');
|
|
59
|
+
if (candidate === '~') return null;
|
|
60
|
+
let expanded = candidate;
|
|
61
|
+
if (candidate.startsWith('~/')) expanded = path.join(userHome, candidate.slice(2));
|
|
62
|
+
if (!path.isAbsolute(expanded)) return null;
|
|
63
|
+
const resolved = path.resolve(expanded);
|
|
64
|
+
if (!resolved || path.parse(resolved).root === resolved) return null;
|
|
65
|
+
return resolved;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function createAgentsMd(target, srcRoot, dryRun) {
|
|
69
|
+
createAgentsMdFromTemplate(
|
|
70
|
+
target,
|
|
71
|
+
resolveAiwgRoot(srcRoot) || srcRoot,
|
|
72
|
+
'grok-build/AGENTS.md.aiwg-template',
|
|
73
|
+
dryRun,
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Kernel skills → `.grok/skills` (always).
|
|
79
|
+
* Standard skills → index-driven by default; `.grok/.aiwg/skills` with `--copy-all`.
|
|
80
|
+
*/
|
|
81
|
+
export function deploySkills(skillDirs, targetDir, opts = {}) {
|
|
82
|
+
const kernelDest = path.join(targetDir, kernelSkillsPath);
|
|
83
|
+
const standardDest = path.join(targetDir, standardSkillsPath);
|
|
84
|
+
return deploySkillsWithKernelRouting(skillDirs, standardDest, kernelDest, {
|
|
85
|
+
...opts,
|
|
86
|
+
copyStandardSkills: opts.copyStandardSkills === true,
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export function deployAgents() {
|
|
91
|
+
return 0;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export function deployRules() {
|
|
95
|
+
return 0;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export async function postDeploy(target, opts = {}) {
|
|
99
|
+
if (opts.createAgentsMd || (!opts.commandsOnly && !opts.skillsOnly && !opts.rulesOnly)) {
|
|
100
|
+
createAgentsMd(target, opts.srcRoot, opts.dryRun);
|
|
101
|
+
}
|
|
102
|
+
if (!opts.quiet) {
|
|
103
|
+
const home = resolveGrokHome(opts.env || process.env);
|
|
104
|
+
console.log(
|
|
105
|
+
`Grok Build (experimental): kernel skills → ${kernelSkillsPath}; ` +
|
|
106
|
+
`standard skills index-driven (opt-in mirror → ${standardSkillsPath} with --copy-all); ` +
|
|
107
|
+
`agents/rules indexed until #2577; user home → ${home || '(unresolved GROK_HOME)'}. ` +
|
|
108
|
+
'Distinct from grokbot.',
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export function getFileExtension() {
|
|
114
|
+
return '.md';
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export async function deploy(opts) {
|
|
118
|
+
const mode = normalizeDeploymentMode(opts.mode);
|
|
119
|
+
const skillDirs = [...getAddonSkillDirs(opts.srcRoot)];
|
|
120
|
+
skillDirs.push(
|
|
121
|
+
...collectFrameworkArtifacts(opts.srcRoot, mode, {
|
|
122
|
+
includeAgents: false,
|
|
123
|
+
includeCommands: false,
|
|
124
|
+
includeSkills: true,
|
|
125
|
+
includeRules: false,
|
|
126
|
+
}).skills,
|
|
127
|
+
);
|
|
128
|
+
let count = 0;
|
|
129
|
+
if (!opts.commandsOnly && !opts.rulesOnly) {
|
|
130
|
+
const result = deploySkills(skillDirs, opts.target, opts);
|
|
131
|
+
count += (result?.kernel ?? 0) + (result?.standardCopied ?? 0);
|
|
132
|
+
}
|
|
133
|
+
await postDeploy(opts.target, opts);
|
|
134
|
+
return count;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export default {
|
|
138
|
+
name,
|
|
139
|
+
aliases,
|
|
140
|
+
paths,
|
|
141
|
+
kernelSkillsPath,
|
|
142
|
+
standardSkillsPath,
|
|
143
|
+
support,
|
|
144
|
+
capabilities,
|
|
145
|
+
resolveGrokHome,
|
|
146
|
+
createAgentsMd,
|
|
147
|
+
deploySkills,
|
|
148
|
+
deployAgents,
|
|
149
|
+
deployRules,
|
|
150
|
+
postDeploy,
|
|
151
|
+
getFileExtension,
|
|
152
|
+
deploy,
|
|
153
|
+
};
|