@aiwg/cli 2026.8.15 → 2026.8.17
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/dist/src/artifacts/browser-export.js +1 -0
- package/dist/src/artifacts/cli.js +1 -1
- package/dist/src/artifacts/fortemi-core-query-adapter.js +6 -0
- package/dist/src/artifacts/index-builder.js +4 -64
- package/dist/src/artifacts/index-files.js +80 -0
- package/dist/src/artifacts/stats.js +22 -50
- package/dist/src/artifacts/types.js +73 -1
- package/dist/src/cli/agent-spawn.js +4 -2
- package/dist/src/cli/handlers/cockpit.js +41 -0
- package/dist/src/cli/handlers/help.js +1 -1
- package/dist/src/cli/handlers/ralph.js +2 -1
- package/dist/src/cli/handlers/sdlc-accelerate.js +2 -1
- package/dist/src/cli/handlers/use.js +117 -12
- package/dist/src/cli/handlers/workspace.js +24 -2
- package/dist/src/cli/services/deployment-verification.js +9 -2
- package/dist/src/cockpit/doctor.js +257 -0
- package/dist/src/config/aiwg-config.js +36 -3
- package/dist/src/providers/transformation-receipt-integration.js +130 -3
- package/dist/src/security/artifact-verifier.js +7 -1
- package/dist/src/serve/pty-bridge.js +6 -11
- package/dist/src/skills/run.js +15 -6
- package/package.json +2 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { createHash } from 'node:crypto';
|
|
2
|
-
import { access, lstat, readFile, readdir } from 'node:fs/promises';
|
|
1
|
+
import { createHash, randomUUID } from 'node:crypto';
|
|
2
|
+
import { access, lstat, mkdir, readFile, readdir, rename, rm, writeFile } from 'node:fs/promises';
|
|
3
3
|
import { homedir } from 'node:os';
|
|
4
4
|
import path from 'node:path';
|
|
5
5
|
import { readAiwgConfig } from '../config/aiwg-config.js';
|
|
@@ -25,6 +25,65 @@ const FRAMEWORK_BUNDLE_DIRS = {
|
|
|
25
25
|
validation: 'validation-complete',
|
|
26
26
|
'knowledge-base': 'knowledge-base',
|
|
27
27
|
};
|
|
28
|
+
const PROVIDER_TRANSFORMATION_EVIDENCE_STATE_SCHEMA = 'aiwg.provider-transformation-evidence-state.v1';
|
|
29
|
+
export function providerTransformationEvidenceStatePath(projectRoot, provider, scope) {
|
|
30
|
+
const receiptPath = providerTransformationReceiptPath(projectRoot, provider, scope);
|
|
31
|
+
return receiptPath.replace(/\.json$/, '.evidence.json');
|
|
32
|
+
}
|
|
33
|
+
function validateEvidenceState(value) {
|
|
34
|
+
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
35
|
+
throw new Error('evidence state must be an object');
|
|
36
|
+
const state = value;
|
|
37
|
+
if (state.schemaVersion !== PROVIDER_TRANSFORMATION_EVIDENCE_STATE_SCHEMA)
|
|
38
|
+
throw new Error('unsupported evidence state schema');
|
|
39
|
+
if (!Number.isFinite(Date.parse(state.recordedAt)))
|
|
40
|
+
throw new Error('recordedAt must be an RFC 3339 date-time');
|
|
41
|
+
if (state.scope !== 'project' && state.scope !== 'user')
|
|
42
|
+
throw new Error('scope must be project or user');
|
|
43
|
+
if (!['local-source', 'source-unavailable', 'verification-failed'].includes(state.disposition)) {
|
|
44
|
+
throw new Error('unsupported source evidence disposition');
|
|
45
|
+
}
|
|
46
|
+
if (!state.provider || state.provider.includes('/') || state.provider.includes('\\'))
|
|
47
|
+
throw new Error('provider is invalid');
|
|
48
|
+
return state;
|
|
49
|
+
}
|
|
50
|
+
async function writeEvidenceState(options, disposition) {
|
|
51
|
+
const provider = normalizeProviderDefinitionId(options.provider) ?? options.provider;
|
|
52
|
+
const target = providerTransformationEvidenceStatePath(options.projectRoot, provider, options.scope);
|
|
53
|
+
await mkdir(path.dirname(target), { recursive: true, mode: 0o700 });
|
|
54
|
+
const temporary = path.join(path.dirname(target), `.${path.basename(target)}.${randomUUID()}.tmp`);
|
|
55
|
+
const state = {
|
|
56
|
+
schemaVersion: PROVIDER_TRANSFORMATION_EVIDENCE_STATE_SCHEMA,
|
|
57
|
+
recordedAt: options.generatedAt ?? new Date().toISOString(),
|
|
58
|
+
provider,
|
|
59
|
+
scope: options.scope,
|
|
60
|
+
disposition,
|
|
61
|
+
};
|
|
62
|
+
try {
|
|
63
|
+
await writeFile(temporary, `${JSON.stringify(state, null, 2)}\n`, { encoding: 'utf8', mode: 0o600, flag: 'wx' });
|
|
64
|
+
await rename(temporary, target);
|
|
65
|
+
}
|
|
66
|
+
catch (error) {
|
|
67
|
+
await rm(temporary, { force: true }).catch(() => undefined);
|
|
68
|
+
throw error;
|
|
69
|
+
}
|
|
70
|
+
await rm(providerTransformationReceiptPath(options.projectRoot, provider, options.scope), { force: true });
|
|
71
|
+
return target;
|
|
72
|
+
}
|
|
73
|
+
async function readEvidenceState(options) {
|
|
74
|
+
const provider = normalizeProviderDefinitionId(options.provider) ?? options.provider;
|
|
75
|
+
try {
|
|
76
|
+
const state = validateEvidenceState(JSON.parse(await readFile(providerTransformationEvidenceStatePath(options.projectRoot, provider, options.scope), 'utf8')));
|
|
77
|
+
if (state.provider !== provider || state.scope !== options.scope)
|
|
78
|
+
throw new Error('evidence state identity does not match deployment');
|
|
79
|
+
return state;
|
|
80
|
+
}
|
|
81
|
+
catch (error) {
|
|
82
|
+
if (error.code === 'ENOENT')
|
|
83
|
+
return null;
|
|
84
|
+
throw error;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
28
87
|
function sha256(value) {
|
|
29
88
|
return createHash('sha256').update(value).digest('hex');
|
|
30
89
|
}
|
|
@@ -225,6 +284,17 @@ function receiptBundles(installed, options) {
|
|
|
225
284
|
.sort();
|
|
226
285
|
return deployed.length > 0 ? deployed : [...new Set(options.requestedBundles)].sort();
|
|
227
286
|
}
|
|
287
|
+
/**
|
|
288
|
+
* Return whether the deployed provider surface includes project-local source
|
|
289
|
+
* material that cannot be authenticated by an AIWG signed web release.
|
|
290
|
+
*/
|
|
291
|
+
export async function providerReceiptHasLocalSources(rawOptions) {
|
|
292
|
+
const provider = normalizeProviderDefinitionId(rawOptions.provider) ?? rawOptions.provider;
|
|
293
|
+
const options = { ...rawOptions, provider };
|
|
294
|
+
const installed = await installedEntries(options);
|
|
295
|
+
return receiptBundles(installed, options)
|
|
296
|
+
.some(bundle => installed[bundle]?.source === 'project-local');
|
|
297
|
+
}
|
|
228
298
|
/**
|
|
229
299
|
* Convert an already signature-verified web release into the stable verifier
|
|
230
300
|
* result contract for the complete canonical bundle consumed by deployment.
|
|
@@ -375,6 +445,27 @@ export async function resolveProviderReceiptRuntimeEvidence(rawOptions) {
|
|
|
375
445
|
};
|
|
376
446
|
}
|
|
377
447
|
export async function finalizeProviderTransformationReceipt(options) {
|
|
448
|
+
if (options.sourceDisposition) {
|
|
449
|
+
const evidenceStatePath = await writeEvidenceState(options, options.sourceDisposition);
|
|
450
|
+
if (options.sourceDisposition === 'local-source') {
|
|
451
|
+
return {
|
|
452
|
+
status: 'policy-exempt',
|
|
453
|
+
receiptPath: null,
|
|
454
|
+
evidenceStatePath,
|
|
455
|
+
outputCount: 0,
|
|
456
|
+
reason: 'local-source development deployments are exempt from signed-release receipt issuance',
|
|
457
|
+
};
|
|
458
|
+
}
|
|
459
|
+
return {
|
|
460
|
+
status: options.sourceDisposition === 'source-unavailable' ? 'source-unavailable' : 'skipped',
|
|
461
|
+
receiptPath: null,
|
|
462
|
+
evidenceStatePath,
|
|
463
|
+
outputCount: 0,
|
|
464
|
+
reason: options.sourceDisposition === 'source-unavailable'
|
|
465
|
+
? 'verified signed-release source evidence is not available from cache or configured resource access'
|
|
466
|
+
: 'canonical source verification failed',
|
|
467
|
+
};
|
|
468
|
+
}
|
|
378
469
|
if (!options.sourceVerifications) {
|
|
379
470
|
return {
|
|
380
471
|
status: 'skipped',
|
|
@@ -412,9 +503,11 @@ export async function finalizeProviderTransformationReceipt(options) {
|
|
|
412
503
|
transformer: evidence.transformer,
|
|
413
504
|
outputPaths: evidence.outputPaths,
|
|
414
505
|
});
|
|
506
|
+
const receiptPath = await writeProviderTransformationReceipt(options.projectRoot, receipt);
|
|
507
|
+
await rm(providerTransformationEvidenceStatePath(options.projectRoot, evidence.provider, options.scope), { force: true });
|
|
415
508
|
return {
|
|
416
509
|
status: 'written',
|
|
417
|
-
receiptPath
|
|
510
|
+
receiptPath,
|
|
418
511
|
outputCount: receipt.outputs.length,
|
|
419
512
|
};
|
|
420
513
|
}
|
|
@@ -425,6 +518,40 @@ export async function diagnoseIntegratedProviderTransformationReceipt(options) {
|
|
|
425
518
|
await access(receiptPath);
|
|
426
519
|
}
|
|
427
520
|
catch {
|
|
521
|
+
const state = await readEvidenceState(options);
|
|
522
|
+
if (state?.disposition === 'local-source') {
|
|
523
|
+
return {
|
|
524
|
+
status: 'policy-exempt',
|
|
525
|
+
receiptPath,
|
|
526
|
+
checkedOutputs: 0,
|
|
527
|
+
findings: [{
|
|
528
|
+
kind: 'policy-exempt',
|
|
529
|
+
message: 'This local-source development deployment is explicitly exempt from signed-release transformation receipts.',
|
|
530
|
+
}],
|
|
531
|
+
};
|
|
532
|
+
}
|
|
533
|
+
if (state?.disposition === 'source-unavailable') {
|
|
534
|
+
return {
|
|
535
|
+
status: 'source-evidence-unavailable',
|
|
536
|
+
receiptPath,
|
|
537
|
+
checkedOutputs: 0,
|
|
538
|
+
findings: [{
|
|
539
|
+
kind: 'source-evidence-unavailable',
|
|
540
|
+
message: 'The deployment succeeded, but verified signed-release source evidence was unavailable from cache or configured resource access.',
|
|
541
|
+
}],
|
|
542
|
+
};
|
|
543
|
+
}
|
|
544
|
+
if (state?.disposition === 'verification-failed') {
|
|
545
|
+
return {
|
|
546
|
+
status: 'drifted',
|
|
547
|
+
receiptPath,
|
|
548
|
+
checkedOutputs: 0,
|
|
549
|
+
findings: [{
|
|
550
|
+
kind: 'source-verification-failure',
|
|
551
|
+
message: 'Canonical source verification failed during receipt finalization.',
|
|
552
|
+
}],
|
|
553
|
+
};
|
|
554
|
+
}
|
|
428
555
|
return {
|
|
429
556
|
status: 'missing-receipt',
|
|
430
557
|
receiptPath,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ARTIFACT_TRUST_STATE_SCHEMA_VERSION, channelStateKey, decodeBase64, dssePae, isIdentityRevoked, parseTrustRoot, publicKeyFingerprint, scopeMatches, selectDelegations, sha256, validateTrustState, verifyBytes, } from './artifact-trust.js';
|
|
1
|
+
import { ARTIFACT_TRUST_STATE_SCHEMA_VERSION, canonicalJson, channelStateKey, decodeBase64, dssePae, isIdentityRevoked, parseTrustRoot, publicKeyFingerprint, scopeMatches, selectDelegations, sha256, validateTrustState, verifyBytes, } from './artifact-trust.js';
|
|
2
2
|
export const ARTIFACT_VERIFICATION_RESULT_SCHEMA_VERSION = 'aiwg.verify.result.v1';
|
|
3
3
|
export const ARTIFACT_VERIFICATION_EXIT_CODES = {
|
|
4
4
|
verified: 0,
|
|
@@ -351,6 +351,12 @@ export async function verifyArtifact(input) {
|
|
|
351
351
|
identities: allAuthenticated.map(identity => identity.id).sort(),
|
|
352
352
|
});
|
|
353
353
|
}
|
|
354
|
+
if (!Buffer.from(canonicalJson(statement), 'utf8').equals(payload)) {
|
|
355
|
+
return result('mismatched', input, [{ code: 'NONCANONICAL_SIGNED_PAYLOAD', message: 'Signed provenance payload is not canonical JSON' }], {
|
|
356
|
+
...common,
|
|
357
|
+
identities: allAuthenticated.map(identity => identity.id).sort(),
|
|
358
|
+
});
|
|
359
|
+
}
|
|
354
360
|
const scopeInput = {
|
|
355
361
|
assetType: statement.predicate.assetType,
|
|
356
362
|
namespace: statement.predicate.publisher.namespace,
|
|
@@ -695,19 +695,14 @@ export async function handlePtyConnection(sessionId, ws, command = 'aiwg', cmdAr
|
|
|
695
695
|
}
|
|
696
696
|
}
|
|
697
697
|
else if (!session.exited) {
|
|
698
|
-
// Reconnect to existing session
|
|
698
|
+
// Reconnect to an existing session by replaying the complete retained
|
|
699
|
+
// buffer. Do not trim at the last full-screen erase: terminal erase
|
|
700
|
+
// sequences repaint the current viewport but xterm still needs the bytes
|
|
701
|
+
// that preceded them to reconstruct scrollback when a user switches away
|
|
702
|
+
// from a session and later returns (#2146).
|
|
699
703
|
registry.addClient(sessionId, clientId, ws);
|
|
700
704
|
if (session.outputBuffer) {
|
|
701
|
-
|
|
702
|
-
// screen-init sequences (cursor moves, status-bar paint) from before the
|
|
703
|
-
// erase don't render as literal garbage in a fresh xterm.js context.
|
|
704
|
-
// Everything before \x1b[2J would be cleared by the erase anyway;
|
|
705
|
-
// everything after is the session content tmux redrew (MOTD, history, etc).
|
|
706
|
-
// If no erase is found, replay the whole buffer unchanged.
|
|
707
|
-
const ERASE = '\x1b[2J';
|
|
708
|
-
const lastErase = session.outputBuffer.lastIndexOf(ERASE);
|
|
709
|
-
const replay = lastErase !== -1 ? session.outputBuffer.slice(lastErase) : session.outputBuffer;
|
|
710
|
-
ws.send(JSON.stringify({ type: 'data', payload: replay }));
|
|
705
|
+
ws.send(JSON.stringify({ type: 'data', payload: session.outputBuffer }));
|
|
711
706
|
}
|
|
712
707
|
}
|
|
713
708
|
else {
|
package/dist/src/skills/run.js
CHANGED
|
@@ -25,6 +25,8 @@ import { spawn } from 'node:child_process';
|
|
|
25
25
|
import { promises as fs } from 'node:fs';
|
|
26
26
|
import * as path from 'node:path';
|
|
27
27
|
import { resolveRuntime, supportedRuntimes } from './runtime.js';
|
|
28
|
+
import { recordTypeForEntry, stableRecordId } from '../artifacts/browser-export.js';
|
|
29
|
+
import { loadFortemiCoreMetadataEntries } from '../artifacts/fortemi-core-query-adapter.js';
|
|
28
30
|
/**
|
|
29
31
|
* Resolve the AIWG installation root. Prefers `$AIWG_ROOT` env, falls
|
|
30
32
|
* back to the channel manager's framework-root resolver.
|
|
@@ -53,22 +55,29 @@ async function findSkillEntry(cwd, name) {
|
|
|
53
55
|
const reader = await import('../artifacts/index-reader.js');
|
|
54
56
|
const entries = [];
|
|
55
57
|
for (const g of ['framework', 'project', 'codebase']) {
|
|
56
|
-
const
|
|
57
|
-
if (
|
|
58
|
-
entries.push(...
|
|
58
|
+
const canonical = loadFortemiCoreMetadataEntries(cwd, g);
|
|
59
|
+
if (canonical.entries.length > 0) {
|
|
60
|
+
entries.push(...canonical.entries);
|
|
61
|
+
}
|
|
62
|
+
else {
|
|
63
|
+
const idx = reader.loadGraphIndexFile(cwd, 'metadata.json', g);
|
|
64
|
+
if (idx)
|
|
65
|
+
entries.push(...Object.values(idx.entries));
|
|
66
|
+
}
|
|
59
67
|
}
|
|
60
68
|
if (entries.length === 0) {
|
|
61
69
|
const legacy = reader.loadMetadataIndex(cwd);
|
|
62
70
|
if (legacy)
|
|
63
71
|
entries.push(...Object.values(legacy.entries));
|
|
64
72
|
}
|
|
65
|
-
const skills = entries.filter(e => e.type === 'skill');
|
|
73
|
+
const skills = entries.filter(e => e.type === 'skill' || e.type === 'aiwg.skill');
|
|
66
74
|
const needle = name.trim();
|
|
67
75
|
// Basename match — skills are conventionally `<dir>/SKILL.md`
|
|
68
76
|
const matches = skills.filter(e => {
|
|
69
77
|
const dir = path.basename(path.dirname(e.path));
|
|
70
78
|
const stem = path.basename(e.path).replace(/\.[^.]+$/, '');
|
|
71
|
-
|
|
79
|
+
const id = stableRecordId(recordTypeForEntry({ ...e, type: 'skill' }, 'v2'), e.path);
|
|
80
|
+
return id === needle || e.name === needle || dir === needle || stem === needle || e.path === needle;
|
|
72
81
|
});
|
|
73
82
|
if (matches.length === 0)
|
|
74
83
|
return null;
|
|
@@ -248,7 +257,7 @@ export async function main(args, env) {
|
|
|
248
257
|
});
|
|
249
258
|
}
|
|
250
259
|
function printUsage() {
|
|
251
|
-
console.log('Usage: aiwg run skill <name> [--cwd <path>] [-- <args...>]');
|
|
260
|
+
console.log('Usage: aiwg run skill <stable-id-or-name> [--cwd <path>] [-- <args...>]');
|
|
252
261
|
console.log('');
|
|
253
262
|
console.log('Examples:');
|
|
254
263
|
console.log(' aiwg run skill voice-apply -- --voice technical-authority --input draft.md');
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aiwg/cli",
|
|
3
|
-
"version": "2026.8.
|
|
3
|
+
"version": "2026.8.17",
|
|
4
4
|
"description": "Lightweight AIWG CLI for signed, versioned web-backed resources.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -67,6 +67,7 @@
|
|
|
67
67
|
"dependencies": {
|
|
68
68
|
"@fortemi/core": "2026.7.15",
|
|
69
69
|
"@modelcontextprotocol/sdk": "^1.30.0",
|
|
70
|
+
"ajv": "^8.20.0",
|
|
70
71
|
"chalk": "^4.1.2",
|
|
71
72
|
"chokidar": "^4.0.3",
|
|
72
73
|
"commander": "^12.1.0",
|