@aiwg/cli 2026.8.11 → 2026.8.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/bin/aiwg.mjs +2 -0
  2. package/dist/src/api/index.d.ts +6 -0
  3. package/dist/src/api/index.js +6 -0
  4. package/dist/src/cli/handlers/artifact-verify.js +171 -0
  5. package/dist/src/cli/handlers/index.js +3 -1
  6. package/dist/src/cli/handlers/setup-manifest.js +52 -3
  7. package/dist/src/cli/handlers/setup.js +15 -2
  8. package/dist/src/cli/handlers/use.js +64 -0
  9. package/dist/src/cli/services/deployment-verification.js +65 -7
  10. package/dist/src/config/aiwg-config.js +4 -3
  11. package/dist/src/config/cli.js +3 -1
  12. package/dist/src/config/gitignore.js +67 -21
  13. package/dist/src/config/workspace.js +8 -1
  14. package/dist/src/extensions/commands/definitions.js +19 -0
  15. package/dist/src/marketplace/artifact-attestation.js +195 -0
  16. package/dist/src/marketplace/exchange.js +437 -79
  17. package/dist/src/marketplace/provenance-types.js +1 -0
  18. package/dist/src/marketplace/provenance.js +7 -1
  19. package/dist/src/providers/transformation-receipt-integration.js +448 -0
  20. package/dist/src/providers/transformation-receipt.js +215 -0
  21. package/dist/src/resources/web-release.d.ts +11 -0
  22. package/dist/src/resources/web-release.js +61 -6
  23. package/dist/src/security/artifact-attestation.js +117 -0
  24. package/dist/src/security/artifact-trust.js +557 -0
  25. package/dist/src/security/artifact-verifier.js +478 -0
  26. package/dist/src/tracker/capability-protocol.js +7 -2
  27. package/package.json +5 -1
  28. package/schemas/security/aiwg-artifact-attestation.v1.schema.json +106 -0
  29. package/schemas/security/aiwg-artifact-provenance.v1.schema.json +204 -0
  30. package/schemas/security/aiwg-artifact-trust-root.v1.schema.json +59 -0
  31. package/schemas/security/aiwg-artifact-trust-state.v1.schema.json +32 -0
  32. package/schemas/security/aiwg-artifact-verification-result.v1.schema.json +46 -0
  33. package/schemas/security/threat-assessment-input.v1.schema.json +57 -0
  34. package/schemas/security/threat-assessment-report.v1.schema.json +104 -0
@@ -140,7 +140,8 @@ async function handleSet(config, args) {
140
140
  // aiwg config get --project remotes.primary
141
141
  //
142
142
  // Set validates enum membership for known fields (delivery.mode,
143
- // delivery.merge_style, delivery.force_push_policy) before writing.
143
+ // delivery.merge_style, delivery.force_push_policy, remotes.issue_provider)
144
+ // before writing.
144
145
  const ENUM_RULES = {
145
146
  'delivery.mode': ['direct', 'feature-branch', 'pr-required'],
146
147
  'delivery.merge_style': ['rebase-merge', 'squash', 'merge', 'fast-forward-only'],
@@ -149,6 +150,7 @@ const ENUM_RULES = {
149
150
  'delivery.signing.enforce': ['commits', 'tags', 'all'],
150
151
  'delivery.release_signing.format': ['openpgp', 'ssh', 'x509'],
151
152
  'delivery.release_signing.enforce': ['commits', 'tags', 'all'],
153
+ 'remotes.issue_provider': ['gitea', 'github', 'local'],
152
154
  'remotes.tracker_actor.via': ['tea', 'gh', 'mcp', 'api'],
153
155
  'remotes.transport.protocol': ['ssh', 'https'],
154
156
  'repo_maintainer.tiers.local': ['collaborator', 'maintainer', 'admin'],
@@ -9,6 +9,10 @@
9
9
  */
10
10
  import * as fs from 'fs/promises';
11
11
  import * as path from 'path';
12
+ import { execFile } from 'child_process';
13
+ import { promisify } from 'util';
14
+ const execFileAsync = promisify(execFile);
15
+ const GITIGNORE_PROBE_BASENAME = '.aiwg-ignore-probe';
12
16
  // ===========================
13
17
  // Recommended Patterns
14
18
  // ===========================
@@ -62,6 +66,63 @@ export const ALL_RECOMMENDED_PATTERNS = [
62
66
  // ===========================
63
67
  // Core Functions
64
68
  // ===========================
69
+ function isTextuallyCovered(pattern, lines) {
70
+ if (lines.includes(pattern))
71
+ return true;
72
+ if (lines.includes(pattern.replace(/\/$/, '')))
73
+ return true;
74
+ const parts = pattern.split('/').filter(Boolean);
75
+ for (let i = 1; i < parts.length; i++) {
76
+ const parent = parts.slice(0, i).join('/') + '/';
77
+ if (lines.includes(parent) || lines.includes(parent.replace(/\/$/, ''))) {
78
+ return true;
79
+ }
80
+ }
81
+ return false;
82
+ }
83
+ function probePath(pattern) {
84
+ return pattern.endsWith('/') ? `${pattern}${GITIGNORE_PROBE_BASENAME}` : pattern;
85
+ }
86
+ async function isGitWorktree(projectRoot) {
87
+ try {
88
+ const { stdout } = await execFileAsync('git', ['rev-parse', '--is-inside-work-tree'], {
89
+ cwd: projectRoot,
90
+ windowsHide: true,
91
+ });
92
+ return stdout.trim() === 'true';
93
+ }
94
+ catch {
95
+ return false;
96
+ }
97
+ }
98
+ /**
99
+ * Resolve effective ignore coverage through Git when possible. This honors
100
+ * negations, .git/info/exclude, and core.excludesFile. A textual fallback keeps
101
+ * the helper deterministic outside Git worktrees or when Git is unavailable.
102
+ *
103
+ * @implements #2106
104
+ */
105
+ async function resolveCoveredPatterns(projectRoot, patterns, lines) {
106
+ if (!(await isGitWorktree(projectRoot))) {
107
+ return new Set(patterns.filter(pattern => isTextuallyCovered(pattern, lines)));
108
+ }
109
+ const covered = await Promise.all(patterns.map(async (pattern) => {
110
+ try {
111
+ await execFileAsync('git', ['check-ignore', '--quiet', '--', probePath(pattern)], {
112
+ cwd: projectRoot,
113
+ windowsHide: true,
114
+ });
115
+ return true;
116
+ }
117
+ catch (error) {
118
+ const code = error.code;
119
+ if (code === 1)
120
+ return false;
121
+ return isTextuallyCovered(pattern, lines);
122
+ }
123
+ }));
124
+ return new Set(patterns.filter((_, index) => covered[index]));
125
+ }
65
126
  /**
66
127
  * Check which recommended patterns are missing from the project's .gitignore.
67
128
  */
@@ -77,26 +138,10 @@ export async function checkGitignore(projectRoot) {
77
138
  // File doesn't exist — all patterns are missing
78
139
  }
79
140
  const lines = content.split('\n').map(l => l.trim());
80
- const isCovered = (pattern) => {
81
- // Exact match
82
- if (lines.includes(pattern))
83
- return true;
84
- // Pattern without trailing slash is also sufficient
85
- if (lines.includes(pattern.replace(/\/$/, '')))
86
- return true;
87
- // Parent directory is ignored (covers all children)
88
- const parts = pattern.split('/').filter(Boolean);
89
- for (let i = 1; i < parts.length; i++) {
90
- const parent = parts.slice(0, i).join('/') + '/';
91
- if (lines.includes(parent) || lines.includes(parent.replace(/\/$/, ''))) {
92
- return true;
93
- }
94
- }
95
- return false;
96
- };
97
- const missingRuntime = AIWG_RUNTIME_PATTERNS.filter(p => !isCovered(p));
98
- const missingSession = CLAUDE_SESSION_PATTERNS.filter(p => !isCovered(p));
99
- const missingProvider = PROVIDER_CONVENTIONAL_PATTERNS.filter(p => !isCovered(p));
141
+ const covered = await resolveCoveredPatterns(projectRoot, ALL_RECOMMENDED_PATTERNS, lines);
142
+ const missingRuntime = AIWG_RUNTIME_PATTERNS.filter(p => !covered.has(p));
143
+ const missingSession = CLAUDE_SESSION_PATTERNS.filter(p => !covered.has(p));
144
+ const missingProvider = PROVIDER_CONVENTIONAL_PATTERNS.filter(p => !covered.has(p));
100
145
  const missing = [...missingRuntime, ...missingSession, ...missingProvider];
101
146
  return { exists, missing, missingRuntime, missingSession, missingProvider };
102
147
  }
@@ -116,10 +161,11 @@ export async function appendGitignore(projectRoot, patterns) {
116
161
  // File doesn't exist, will create
117
162
  }
118
163
  const existingLines = existing.split('\n').map(l => l.trim());
164
+ const covered = await resolveCoveredPatterns(projectRoot, patterns, existingLines);
119
165
  const toAdd = [];
120
166
  const alreadyPresent = [];
121
167
  for (const pattern of patterns) {
122
- if (existingLines.includes(pattern) || existingLines.includes(pattern.replace(/\/$/, ''))) {
168
+ if (covered.has(pattern)) {
123
169
  alreadyPresent.push(pattern);
124
170
  }
125
171
  else {
@@ -119,14 +119,21 @@ async function resolveEndpoint(repoPath, remote, providerHint) {
119
119
  : 'unknown',
120
120
  };
121
121
  }
122
+ function trackerProviderHint(remotes, fallback) {
123
+ const configured = remotes.issue_provider;
124
+ if (configured === 'gitea' || configured === 'github')
125
+ return configured;
126
+ return fallback;
127
+ }
122
128
  async function resolveMember(entry, workspaceRoot) {
123
129
  const memberPath = resolveFrom(workspaceRoot, entry.path);
124
130
  const configPath = getConfigPath(memberPath);
125
131
  const config = await readAiwgConfig(memberPath);
126
132
  const remotes = resolveRemotes(config?.remotes);
133
+ const issueProviderHint = trackerProviderHint(remotes, entry.provider);
127
134
  const [primary, issueTracker, ci] = await Promise.all([
128
135
  resolveEndpoint(memberPath, remotes.primary, entry.provider),
129
- resolveEndpoint(memberPath, remotes.issue_tracker, entry.provider),
136
+ resolveEndpoint(memberPath, remotes.issue_tracker, issueProviderHint),
130
137
  resolveEndpoint(memberPath, remotes.ci, entry.provider),
131
138
  ]);
132
139
  const drift = [];
@@ -2478,6 +2478,24 @@ export const evidenceCommand = {
2478
2478
  },
2479
2479
  },
2480
2480
  };
2481
+ export const artifactVerifyCommand = {
2482
+ id: 'verify',
2483
+ type: 'command',
2484
+ name: 'Artifact Verification',
2485
+ description: 'Verify cross-asset DSSE provenance and manage versioned trust roots',
2486
+ version: '1.0.0',
2487
+ capabilities: ['cli', 'security', 'dsse', 'sigstore', 'provenance', 'trust-policy'],
2488
+ keywords: ['verify', 'artifact', 'attestation', 'dsse', 'sigstore', 'trust-root', 'revocation'],
2489
+ category: 'utility',
2490
+ platforms: { claude: 'full', generic: 'full' },
2491
+ deployment: { pathTemplate: '.{platform}/commands/{id}.md', core: true },
2492
+ metadata: {
2493
+ type: 'command',
2494
+ template: 'utility',
2495
+ argumentHint: '<artifact> --attestation <path> --policy <root.json> [--offline] [--json] | trust <bootstrap|update|status>',
2496
+ allowedTools: ['Read', 'Bash'],
2497
+ },
2498
+ };
2481
2499
  export const costReportCommand = {
2482
2500
  id: 'cost-report',
2483
2501
  type: 'skill',
@@ -3614,6 +3632,7 @@ export const commandDefinitions = [
3614
3632
  teamCommand,
3615
3633
  // Metrics (4)
3616
3634
  evidenceCommand,
3635
+ artifactVerifyCommand,
3617
3636
  costReportCommand,
3618
3637
  costHistoryCommand,
3619
3638
  metricsTokensCommand,
@@ -0,0 +1,195 @@
1
+ import { createArtifactAttestation, serializeArtifactAttestation, } from '../security/artifact-attestation.js';
2
+ import { verifyArtifact, } from '../security/artifact-verifier.js';
3
+ import { canonicalJson as artifactCanonicalJson, parseTrustRoot, sha256 as artifactSha256, } from '../security/artifact-trust.js';
4
+ import { canonicalJson, createPackageLock, verifyProvenanceEnvelope, } from './provenance.js';
5
+ export const MARKETPLACE_ENVELOPE_SUBJECT = 'aiwg-marketplace-envelope.json';
6
+ export function serializeMarketplaceEnvelope(envelope) {
7
+ return Buffer.from(`${canonicalJson(envelope)}\n`, 'utf8');
8
+ }
9
+ export function marketplaceArtifactScope(envelope) {
10
+ return {
11
+ assetType: 'marketplace-envelope',
12
+ namespace: envelope.package.namespace,
13
+ channel: `marketplace:${envelope.package.name}`,
14
+ };
15
+ }
16
+ function requiredMaterialNames(materials) {
17
+ const names = new Set(materials.map(material => material.name));
18
+ for (const required of ['lock', 'inventory', 'git-tree', 'fortemi-shard', 'receipt', 'sbom', 'license']) {
19
+ if (!names.has(required))
20
+ throw new Error(`Marketplace attestation requires '${required}' material`);
21
+ }
22
+ }
23
+ export function createMarketplaceArtifactAttestation(options) {
24
+ requiredMaterialNames(options.materials);
25
+ const scope = marketplaceArtifactScope(options.envelope);
26
+ const envelopeBytes = serializeMarketplaceEnvelope(options.envelope);
27
+ const provenanceBytes = Buffer.from(artifactCanonicalJson(options.envelope.provenance), 'utf8');
28
+ const dependencies = options.envelope.package.dependencies
29
+ .filter(dependency => !dependency.optional)
30
+ .map(dependency => {
31
+ if (!dependency.lockId)
32
+ throw new Error(`Required dependency '${dependency.identity}' has no immutable lockId`);
33
+ return {
34
+ name: dependency.identity,
35
+ uri: `aiwg:marketplace-lock:${dependency.lockId}`,
36
+ mediaType: 'application/vnd.aiwg.marketplace-lock.v1+json',
37
+ digest: { sha256: dependency.lockId.replace(/^sha256:/, '') },
38
+ };
39
+ });
40
+ const attestation = createArtifactAttestation({
41
+ artifact: {
42
+ name: MARKETPLACE_ENVELOPE_SUBJECT,
43
+ bytes: envelopeBytes,
44
+ mediaType: 'application/vnd.aiwg.marketplace-envelope.v1+json',
45
+ },
46
+ assetType: scope.assetType,
47
+ publisher: {
48
+ id: options.publisherIdentity ?? options.envelope.publisher.id,
49
+ namespace: scope.namespace,
50
+ role: 'marketplace-publisher',
51
+ },
52
+ publication: {
53
+ version: options.envelope.package.version,
54
+ channel: scope.channel,
55
+ sequence: options.envelope.publication.sequence,
56
+ sourceUri: options.envelope.source.canonicalRemote,
57
+ },
58
+ issuedAt: options.issuedAt ?? options.envelope.publication.publishedAt,
59
+ expiresAt: options.expiresAt,
60
+ derivation: {
61
+ builder: options.builder,
62
+ materials: options.materials.map(material => ({
63
+ name: material.name,
64
+ uri: material.uri,
65
+ ...(material.mediaType ? { mediaType: material.mediaType } : {}),
66
+ digest: { sha256: artifactSha256(material.bytes) },
67
+ })),
68
+ reproducible: true,
69
+ },
70
+ provenanceGraph: {
71
+ standard: 'W3C-PROV',
72
+ uri: 'aiwg:marketplace-provenance:w3c-prov',
73
+ sha256: artifactSha256(provenanceBytes),
74
+ },
75
+ dependencies,
76
+ privateKey: options.privateKey,
77
+ });
78
+ return { attestation, materials: options.materials };
79
+ }
80
+ export function serializeMarketplaceAttestation(evidence) {
81
+ return serializeArtifactAttestation(evidence.attestation);
82
+ }
83
+ function signedMarketplacePolicy(root) {
84
+ return root.signed.policy.marketplace ?? {
85
+ evidenceMode: 'marketplace-only',
86
+ legacySignatureMigrationGate: false,
87
+ recursiveDependencies: 'if-present',
88
+ };
89
+ }
90
+ export async function verifyMarketplaceEvidence(options) {
91
+ const authenticatedRoot = options.artifact ? parseTrustRoot(options.artifact.rootBytes) : undefined;
92
+ const signedPolicy = authenticatedRoot ? signedMarketplacePolicy(authenticatedRoot) : {
93
+ evidenceMode: 'marketplace-only',
94
+ legacySignatureMigrationGate: false,
95
+ recursiveDependencies: 'if-present',
96
+ };
97
+ const legacyRequired = signedPolicy.evidenceMode !== 'cross-asset-required'
98
+ || !signedPolicy.legacySignatureMigrationGate;
99
+ const marketplace = await verifyProvenanceEnvelope({
100
+ envelope: options.envelope,
101
+ contentRoot: options.contentRoot,
102
+ checkoutPath: options.checkoutPath,
103
+ trustStore: options.trustStore,
104
+ installedLocks: options.installedLocks,
105
+ at: options.artifact?.now ? new Date(options.artifact.now) : undefined,
106
+ policy: legacyRequired
107
+ ? {
108
+ requireSignature: true,
109
+ allowIntegrityOnly: false,
110
+ requireDependencyLocks: signedPolicy.recursiveDependencies === 'required',
111
+ ...options.marketplacePolicy,
112
+ }
113
+ : {
114
+ requireDependencyLocks: signedPolicy.recursiveDependencies === 'required',
115
+ ...options.marketplacePolicy,
116
+ },
117
+ });
118
+ const errors = marketplace.ok ? [] : marketplace.errors.map(error => `marketplace: ${error}`);
119
+ let crossAsset;
120
+ const crossRequired = signedPolicy.evidenceMode !== 'marketplace-only';
121
+ if (options.artifact) {
122
+ const scope = marketplaceArtifactScope(options.envelope);
123
+ crossAsset = await verifyArtifact({
124
+ artifactBytes: serializeMarketplaceEnvelope(options.envelope),
125
+ artifactName: MARKETPLACE_ENVELOPE_SUBJECT,
126
+ attestation: options.artifact.evidence.attestation,
127
+ rootBytes: options.artifact.rootBytes,
128
+ state: options.artifact.state,
129
+ materials: new Map(options.artifact.evidence.materials.map(material => [material.uri, material.bytes])),
130
+ expectedScope: scope,
131
+ offline: options.artifact.offline,
132
+ now: options.artifact.now,
133
+ });
134
+ if (crossAsset.status !== 'verified')
135
+ errors.push(`cross-asset: ${crossAsset.status}`);
136
+ }
137
+ else if (crossRequired) {
138
+ errors.push('cross-asset: required evidence is unavailable');
139
+ }
140
+ if (legacyRequired && marketplace.status !== 'verified')
141
+ errors.push('marketplace: a trusted legacy publisher signature remains mandatory');
142
+ if (crossAsset?.status === 'verified' && marketplace.signer && options.trustStore) {
143
+ const legacyKey = options.trustStore.keys.find(key => key.keyId === marketplace.signer);
144
+ if (!legacyKey?.artifactIdentityId || !crossAsset.identities.includes(legacyKey.artifactIdentityId)) {
145
+ errors.push('trust-parity: marketplace and cross-asset verification resolved different publisher authorities');
146
+ }
147
+ }
148
+ return {
149
+ ok: errors.length === 0,
150
+ status: errors.length === 0 ? 'verified' : 'failed',
151
+ marketplace,
152
+ ...(crossAsset ? { crossAsset } : {}),
153
+ errors: [...new Set(errors)],
154
+ };
155
+ }
156
+ export async function verifyMarketplaceDependencyClosure(options) {
157
+ const errors = [];
158
+ const verified = new Set();
159
+ const active = new Set();
160
+ const visit = async (envelope) => {
161
+ const lock = createPackageLock(envelope, envelope.publication.publishedAt);
162
+ if (verified.has(lock.lockId))
163
+ return;
164
+ if (active.has(lock.lockId)) {
165
+ errors.push(`dependency cycle detected at ${lock.lockId}`);
166
+ return;
167
+ }
168
+ active.add(lock.lockId);
169
+ if (!await options.verify(envelope))
170
+ errors.push(`dependency evidence failed for ${lock.lockId}`);
171
+ for (const dependency of envelope.package.dependencies.filter(item => !item.optional)) {
172
+ if (!dependency.lockId) {
173
+ if (options.required)
174
+ errors.push(`required dependency '${dependency.identity}' has no immutable lockId`);
175
+ continue;
176
+ }
177
+ const child = options.packages.get(dependency.lockId);
178
+ if (!child) {
179
+ errors.push(`required dependency '${dependency.identity}' is unavailable at ${dependency.lockId}`);
180
+ continue;
181
+ }
182
+ const actual = createPackageLock(child, child.publication.publishedAt).lockId;
183
+ if (actual !== dependency.lockId) {
184
+ errors.push(`dependency substitution for '${dependency.identity}': expected ${dependency.lockId}, got ${actual}`);
185
+ continue;
186
+ }
187
+ await visit(child);
188
+ }
189
+ active.delete(lock.lockId);
190
+ verified.add(lock.lockId);
191
+ };
192
+ await visit(options.root);
193
+ return { ok: errors.length === 0, errors, verifiedLocks: [...verified].sort() };
194
+ }
195
+ //# sourceMappingURL=artifact-attestation.js.map