@aiwg/cli 2026.7.20 → 2026.7.23
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/README.md +18 -7
- package/dist/src/api/index.d.ts +2 -0
- package/dist/src/api/index.js +2 -0
- package/dist/src/artifacts/browser-export.js +7 -0
- package/dist/src/artifacts/citation-parser.js +96 -35
- package/dist/src/artifacts/index-builder.js +54 -17
- package/dist/src/artifacts/state-transfer.js +27 -0
- package/dist/src/artifacts/stats.js +8 -0
- package/dist/src/cli/cli-extension-loader.js +73 -0
- package/dist/src/cli/handlers/index.js +3 -1
- package/dist/src/cli/handlers/sessions.js +1265 -0
- package/dist/src/cli/handlers/skill-lint.js +49 -45
- package/dist/src/cli/handlers/use.js +143 -60
- package/dist/src/cli/handlers/utilities.js +22 -8
- package/dist/src/cli/skill-usage.js +146 -24
- package/dist/src/config/aiwg-config.js +12 -0
- package/dist/src/config/cli.js +16 -3
- package/dist/src/extensions/commands/definitions.js +29 -0
- package/dist/src/extensions/manifest.js +29 -0
- package/dist/src/security/threat-assessment-config.js +296 -0
- package/dist/src/sessions/adapters/claude.js +385 -0
- package/dist/src/sessions/adapters/codex.js +548 -0
- package/dist/src/sessions/adapters/copilot.js +226 -0
- package/dist/src/sessions/adapters/cursor.js +528 -0
- package/dist/src/sessions/adapters/factory.js +386 -0
- package/dist/src/sessions/adapters/generic.js +225 -0
- package/dist/src/sessions/adapters/hermes.js +341 -0
- package/dist/src/sessions/adapters/openclaw.js +381 -0
- package/dist/src/sessions/adapters/opencode.js +454 -0
- package/dist/src/sessions/adapters/openhuman.js +315 -0
- package/dist/src/sessions/adapters/warp.js +160 -0
- package/dist/src/sessions/adapters/windsurf.js +212 -0
- package/dist/src/sessions/batch-contracts.js +121 -0
- package/dist/src/sessions/batch-import.js +265 -0
- package/dist/src/sessions/candidates.js +210 -0
- package/dist/src/sessions/contracts.js +337 -0
- package/dist/src/sessions/discovery.js +51 -0
- package/dist/src/sessions/fixtures.js +12 -0
- package/dist/src/sessions/import-lease.js +152 -0
- package/dist/src/sessions/importer.js +464 -0
- package/dist/src/sessions/index.js +31 -0
- package/dist/src/sessions/knowledge-shard.js +61 -0
- package/dist/src/sessions/optional-backends.js +238 -0
- package/dist/src/sessions/origin.js +117 -0
- package/dist/src/sessions/policy.js +192 -0
- package/dist/src/sessions/ports.js +2 -0
- package/dist/src/sessions/promotion.js +367 -0
- package/dist/src/sessions/readers.js +176 -0
- package/dist/src/sessions/repository.js +1892 -0
- package/dist/src/sessions/timeline.js +148 -0
- package/dist/src/sessions/workspace-discovery.js +319 -0
- package/dist/src/skills/adapters/agent-skills.js +59 -0
- package/dist/src/skills/adapters/local.js +19 -1
- package/dist/src/skills/agent-skills.js +249 -0
- package/dist/src/skills/cli.js +463 -7
- package/dist/src/skills/deployer.js +554 -0
- package/dist/src/skills/doctor.js +105 -0
- package/dist/src/skills/exporter.js +382 -0
- package/dist/src/skills/importer.js +921 -0
- package/dist/src/skills/registry.js +19 -0
- package/dist/src/skills/validator.js +323 -0
- package/package.json +2 -2
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
export const BATCH_IMPORT_VERSION = '1.0.0';
|
|
2
|
+
export const COVERAGE_VERSION = '1.0.0';
|
|
3
|
+
export function coverageFromBatchRun(run, now = new Date(), staleAfterMs = 24 * 60 * 60 * 1_000) {
|
|
4
|
+
if (!run)
|
|
5
|
+
return unknownCoverage();
|
|
6
|
+
const coveredStatuses = new Set([
|
|
7
|
+
'committed', 'duplicate', 'previously-committed',
|
|
8
|
+
]);
|
|
9
|
+
const accepted = run.sources.filter((source) => source.status === 'committed').length;
|
|
10
|
+
const rejected = run.sources.filter((source) => source.status === 'rejected').length;
|
|
11
|
+
const skipped = run.sources.filter((source) => source.status === 'skipped').length;
|
|
12
|
+
const duplicated = run.sources.filter((source) => source.status === 'duplicate').length;
|
|
13
|
+
const previouslyCommitted = run.sources.filter((source) => source.status === 'previously-committed').length;
|
|
14
|
+
const pending = run.sources.filter((source) => source.status === 'pending' || source.status === 'running').length;
|
|
15
|
+
const checked = providerNames(run, 'checked');
|
|
16
|
+
const unavailable = providerNames(run, 'unavailable');
|
|
17
|
+
const exportRequired = providerNames(run, 'export-required');
|
|
18
|
+
const notChecked = providerNames(run, 'not-checked');
|
|
19
|
+
const manifestAgeMs = Math.max(0, now.getTime() - Date.parse(run.manifestCreatedAt));
|
|
20
|
+
const isStale = manifestAgeMs > staleAfterMs;
|
|
21
|
+
const incomplete = rejected > 0 || pending > 0 || skipped > 0
|
|
22
|
+
|| exportRequired.length > 0 || notChecked.length > 0;
|
|
23
|
+
const rejectionCounts = {};
|
|
24
|
+
for (const source of run.sources) {
|
|
25
|
+
if (source.status !== 'rejected')
|
|
26
|
+
continue;
|
|
27
|
+
const code = source.errorCode ?? 'UNKNOWN_REJECTION';
|
|
28
|
+
rejectionCounts[code] = (rejectionCounts[code] ?? 0) + 1;
|
|
29
|
+
}
|
|
30
|
+
const sourceTimestamps = run.providers.flatMap((provider) => [
|
|
31
|
+
provider.dateRange.earliest,
|
|
32
|
+
provider.dateRange.latest,
|
|
33
|
+
]).filter((value) => value !== null).sort();
|
|
34
|
+
const importedTimestamps = run.sources
|
|
35
|
+
.filter((source) => coveredStatuses.has(source.status))
|
|
36
|
+
.map((source) => source.updatedAt)
|
|
37
|
+
.sort();
|
|
38
|
+
const remediation = [
|
|
39
|
+
...(rejected > 0
|
|
40
|
+
? ['Run `aiwg sessions import-discovered --resume --confirm` after correcting rejected sources.']
|
|
41
|
+
: []),
|
|
42
|
+
...(exportRequired.length > 0
|
|
43
|
+
? ['Export and explicitly authorize providers marked export-required, then run discovery again.']
|
|
44
|
+
: []),
|
|
45
|
+
...(isStale ? ['Run `aiwg sessions discover` to refresh the stale manifest.'] : []),
|
|
46
|
+
];
|
|
47
|
+
return {
|
|
48
|
+
schemaVersion: COVERAGE_VERSION,
|
|
49
|
+
status: isStale ? 'stale' : incomplete ? 'partial' : 'complete',
|
|
50
|
+
workspaceId: run.workspaceId,
|
|
51
|
+
manifestId: run.manifestId,
|
|
52
|
+
batchRunId: run.runId,
|
|
53
|
+
manifestCreatedAt: run.manifestCreatedAt,
|
|
54
|
+
manifestAgeMs,
|
|
55
|
+
providers: { checked, unavailable, exportRequired, notChecked },
|
|
56
|
+
sources: {
|
|
57
|
+
discovered: run.sources.length,
|
|
58
|
+
accepted,
|
|
59
|
+
rejected,
|
|
60
|
+
skipped,
|
|
61
|
+
duplicated,
|
|
62
|
+
previouslyCommitted,
|
|
63
|
+
pending,
|
|
64
|
+
},
|
|
65
|
+
sessionsAccepted: run.sources.reduce((sum, source) => sum + source.sessionsAccepted, 0),
|
|
66
|
+
eventsAccepted: run.sources.reduce((sum, source) => sum + source.eventsAccepted, 0),
|
|
67
|
+
coverageRatio: run.sources.length === 0
|
|
68
|
+
? null
|
|
69
|
+
: (accepted + duplicated + previouslyCommitted) / run.sources.length,
|
|
70
|
+
rejectionCounts,
|
|
71
|
+
sourceDateRange: {
|
|
72
|
+
earliest: sourceTimestamps.at(0) ?? null,
|
|
73
|
+
latest: sourceTimestamps.at(-1) ?? null,
|
|
74
|
+
},
|
|
75
|
+
importedDateRange: {
|
|
76
|
+
earliest: importedTimestamps.at(0) ?? null,
|
|
77
|
+
latest: importedTimestamps.at(-1) ?? null,
|
|
78
|
+
},
|
|
79
|
+
remediation,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
function providerNames(run, status) {
|
|
83
|
+
return run.providers
|
|
84
|
+
.filter((provider) => provider.status === status)
|
|
85
|
+
.map((provider) => provider.provider)
|
|
86
|
+
.sort();
|
|
87
|
+
}
|
|
88
|
+
function unknownCoverage() {
|
|
89
|
+
return {
|
|
90
|
+
schemaVersion: COVERAGE_VERSION,
|
|
91
|
+
status: 'unknown',
|
|
92
|
+
workspaceId: '',
|
|
93
|
+
manifestId: null,
|
|
94
|
+
batchRunId: null,
|
|
95
|
+
manifestCreatedAt: null,
|
|
96
|
+
manifestAgeMs: null,
|
|
97
|
+
providers: {
|
|
98
|
+
checked: [],
|
|
99
|
+
unavailable: [],
|
|
100
|
+
exportRequired: [],
|
|
101
|
+
notChecked: [],
|
|
102
|
+
},
|
|
103
|
+
sources: {
|
|
104
|
+
discovered: 0,
|
|
105
|
+
accepted: 0,
|
|
106
|
+
rejected: 0,
|
|
107
|
+
skipped: 0,
|
|
108
|
+
duplicated: 0,
|
|
109
|
+
previouslyCommitted: 0,
|
|
110
|
+
pending: 0,
|
|
111
|
+
},
|
|
112
|
+
sessionsAccepted: 0,
|
|
113
|
+
eventsAccepted: 0,
|
|
114
|
+
coverageRatio: null,
|
|
115
|
+
rejectionCounts: {},
|
|
116
|
+
sourceDateRange: { earliest: null, latest: null },
|
|
117
|
+
importedDateRange: { earliest: null, latest: null },
|
|
118
|
+
remediation: ['Run `aiwg sessions discover --workspace <path>` to establish coverage.'],
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
//# sourceMappingURL=batch-contracts.js.map
|
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
import { stat } from 'node:fs/promises';
|
|
2
|
+
import { CLAUDE_ADAPTER_VERSION, ClaudeSessionAdapter, } from './adapters/claude.js';
|
|
3
|
+
import { CODEX_ADAPTER_VERSION, CodexSessionAdapter, } from './adapters/codex.js';
|
|
4
|
+
import { CURSOR_ADAPTER_VERSION, CursorSessionAdapter, } from './adapters/cursor.js';
|
|
5
|
+
import { FACTORY_ADAPTER_VERSION, FactorySessionAdapter, } from './adapters/factory.js';
|
|
6
|
+
import { SESSION_CONTRACT_VERSION, SessionContractError, SessionSourceSchema, sha256, } from './contracts.js';
|
|
7
|
+
import { BATCH_IMPORT_VERSION, coverageFromBatchRun, } from './batch-contracts.js';
|
|
8
|
+
import { IncrementalSessionImporter, SessionImportFailure } from './importer.js';
|
|
9
|
+
import { fingerprintSourceFile } from './readers.js';
|
|
10
|
+
export async function importDiscoveryManifest(options) {
|
|
11
|
+
const now = options.now ?? (() => new Date());
|
|
12
|
+
const runId = sha256([
|
|
13
|
+
'workspace-import-v1',
|
|
14
|
+
options.manifest.manifestId,
|
|
15
|
+
options.manifest.workspaceId,
|
|
16
|
+
].join('\0'));
|
|
17
|
+
const prior = options.repository.getBatchImportRunForManifest(options.manifest.manifestId, options.manifest.workspaceId);
|
|
18
|
+
const run = prior ?? newBatchRun(options.manifest, runId, now());
|
|
19
|
+
if (run.runId !== runId) {
|
|
20
|
+
throw new SessionContractError('IMPORT_CONFLICT', 'discovery manifest is already associated with a different batch run');
|
|
21
|
+
}
|
|
22
|
+
run.status = 'running';
|
|
23
|
+
run.updatedAt = now().toISOString();
|
|
24
|
+
run.completedAt = null;
|
|
25
|
+
options.repository.saveBatchImportRun(run);
|
|
26
|
+
injectFault(options, run, 'run-saved');
|
|
27
|
+
for (const manifestSource of options.manifest.sources) {
|
|
28
|
+
const disposition = run.sources.find((candidate) => candidate.sourceId === manifestSource.sourceId);
|
|
29
|
+
if (!disposition) {
|
|
30
|
+
throw new SessionContractError('IMPORT_CONFLICT', `batch run is missing discovered source ${manifestSource.sourceId}`);
|
|
31
|
+
}
|
|
32
|
+
if (isAccepted(disposition.status)) {
|
|
33
|
+
disposition.status = 'previously-committed';
|
|
34
|
+
disposition.updatedAt = now().toISOString();
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
if (options.signal?.aborted) {
|
|
38
|
+
run.status = 'interrupted';
|
|
39
|
+
run.updatedAt = now().toISOString();
|
|
40
|
+
options.repository.saveBatchImportRun(run);
|
|
41
|
+
throw new SessionContractError('IMPORT_INTERRUPTED', 'workspace batch import was cancelled and can be resumed');
|
|
42
|
+
}
|
|
43
|
+
disposition.status = 'running';
|
|
44
|
+
disposition.attempts += 1;
|
|
45
|
+
disposition.errorCode = null;
|
|
46
|
+
disposition.diagnostic = null;
|
|
47
|
+
disposition.updatedAt = now().toISOString();
|
|
48
|
+
persistProgress(options.repository, run, now());
|
|
49
|
+
try {
|
|
50
|
+
await assertManifestSourceUnchanged(manifestSource);
|
|
51
|
+
const selectedSource = {
|
|
52
|
+
provider: manifestSource.provider,
|
|
53
|
+
locator: manifestSource.locator,
|
|
54
|
+
locatorClass: manifestSource.locatorClass,
|
|
55
|
+
sourceId: manifestSource.sourceId,
|
|
56
|
+
authorizedScope: {
|
|
57
|
+
workspaceId: options.manifest.workspaceId,
|
|
58
|
+
allowedRoots: [manifestSource.authorizedRoot],
|
|
59
|
+
},
|
|
60
|
+
};
|
|
61
|
+
const adapter = adapterFor(manifestSource.provider);
|
|
62
|
+
const source = await sessionSource(selectedSource, adapter);
|
|
63
|
+
const receipts = await new IncrementalSessionImporter(options.repository).import({
|
|
64
|
+
source,
|
|
65
|
+
selectedSource,
|
|
66
|
+
adapter,
|
|
67
|
+
workspaceId: options.manifest.workspaceId,
|
|
68
|
+
policyVersion: '1.0.0',
|
|
69
|
+
batchRunId: run.runId,
|
|
70
|
+
publish: false,
|
|
71
|
+
signal: options.signal,
|
|
72
|
+
inactivityThresholdMs: options.inactivityThresholdMs,
|
|
73
|
+
});
|
|
74
|
+
await assertManifestSourceUnchanged(manifestSource);
|
|
75
|
+
const totals = options.repository.sourceImportTotals(manifestSource.sourceId, run.runId);
|
|
76
|
+
disposition.sessionsAccepted = totals.sessions;
|
|
77
|
+
disposition.eventsAccepted = totals.events;
|
|
78
|
+
disposition.status = receipts.length > 0
|
|
79
|
+
&& receipts.every((receipt) => receipt.outcome === 'duplicate')
|
|
80
|
+
? 'duplicate'
|
|
81
|
+
: receipts.length === 0
|
|
82
|
+
? 'previously-committed'
|
|
83
|
+
: 'committed';
|
|
84
|
+
}
|
|
85
|
+
catch (error) {
|
|
86
|
+
disposition.status = 'rejected';
|
|
87
|
+
disposition.errorCode = importErrorCode(error);
|
|
88
|
+
disposition.diagnostic = [
|
|
89
|
+
`provider=${manifestSource.provider}`,
|
|
90
|
+
`source=${manifestSource.sourceId}`,
|
|
91
|
+
`code=${disposition.errorCode}`,
|
|
92
|
+
].join(' ');
|
|
93
|
+
}
|
|
94
|
+
disposition.updatedAt = now().toISOString();
|
|
95
|
+
persistProgress(options.repository, run, now());
|
|
96
|
+
injectFault(options, run, 'source-staged', manifestSource.sourceId);
|
|
97
|
+
}
|
|
98
|
+
const rejected = run.sources.some((source) => source.status === 'rejected');
|
|
99
|
+
const pending = run.sources.some((source) => source.status === 'pending' || source.status === 'running');
|
|
100
|
+
const providerGap = run.providers.some((provider) => provider.status === 'export-required' || provider.status === 'not-checked');
|
|
101
|
+
const publishable = run.sources
|
|
102
|
+
.filter((source) => isAccepted(source.status))
|
|
103
|
+
.map((source) => source.sourceId);
|
|
104
|
+
injectFault(options, run, 'before-publication');
|
|
105
|
+
options.repository.commitStagedBatch(run.runId, publishable);
|
|
106
|
+
injectFault(options, run, 'after-publication');
|
|
107
|
+
run.status = pending ? 'interrupted' : rejected || providerGap ? 'partial' : 'complete';
|
|
108
|
+
run.updatedAt = now().toISOString();
|
|
109
|
+
run.completedAt = pending ? null : run.updatedAt;
|
|
110
|
+
options.repository.saveBatchImportRun(run);
|
|
111
|
+
return receiptFor(run, now());
|
|
112
|
+
}
|
|
113
|
+
function injectFault(options, run, boundary, sourceId) {
|
|
114
|
+
if (!options.fault)
|
|
115
|
+
return;
|
|
116
|
+
try {
|
|
117
|
+
options.fault(boundary, sourceId);
|
|
118
|
+
}
|
|
119
|
+
catch (error) {
|
|
120
|
+
run.status = 'interrupted';
|
|
121
|
+
run.updatedAt = (options.now ?? (() => new Date()))().toISOString();
|
|
122
|
+
run.completedAt = null;
|
|
123
|
+
options.repository.saveBatchImportRun(run);
|
|
124
|
+
throw error;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
export function previewDiscoveryImport(manifest, existing, now = new Date()) {
|
|
128
|
+
const run = existing ?? newBatchRun(manifest, sha256(['workspace-import-v1', manifest.manifestId, manifest.workspaceId].join('\0')), now);
|
|
129
|
+
return receiptFor(run, now);
|
|
130
|
+
}
|
|
131
|
+
function newBatchRun(manifest, runId, now) {
|
|
132
|
+
const timestamp = now.toISOString();
|
|
133
|
+
return {
|
|
134
|
+
schemaVersion: BATCH_IMPORT_VERSION,
|
|
135
|
+
runId,
|
|
136
|
+
manifestId: manifest.manifestId,
|
|
137
|
+
manifestCreatedAt: manifest.createdAt,
|
|
138
|
+
workspaceId: manifest.workspaceId,
|
|
139
|
+
status: 'running',
|
|
140
|
+
startedAt: timestamp,
|
|
141
|
+
updatedAt: timestamp,
|
|
142
|
+
completedAt: null,
|
|
143
|
+
providers: manifest.providers,
|
|
144
|
+
sources: manifest.sources.map((source) => ({
|
|
145
|
+
sourceId: source.sourceId,
|
|
146
|
+
provider: source.provider,
|
|
147
|
+
status: 'pending',
|
|
148
|
+
attempts: 0,
|
|
149
|
+
sessionsAccepted: 0,
|
|
150
|
+
eventsAccepted: 0,
|
|
151
|
+
errorCode: null,
|
|
152
|
+
diagnostic: null,
|
|
153
|
+
updatedAt: timestamp,
|
|
154
|
+
})),
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
function receiptFor(run, now) {
|
|
158
|
+
const coverage = coverageFromBatchRun(run, now);
|
|
159
|
+
return {
|
|
160
|
+
schemaVersion: BATCH_IMPORT_VERSION,
|
|
161
|
+
run,
|
|
162
|
+
coverage,
|
|
163
|
+
totals: {
|
|
164
|
+
discovered: coverage.sources.discovered,
|
|
165
|
+
accepted: coverage.sources.accepted,
|
|
166
|
+
rejected: coverage.sources.rejected,
|
|
167
|
+
skipped: coverage.sources.skipped,
|
|
168
|
+
duplicated: coverage.sources.duplicated,
|
|
169
|
+
previouslyCommitted: coverage.sources.previouslyCommitted,
|
|
170
|
+
pending: coverage.sources.pending,
|
|
171
|
+
sessionsAccepted: coverage.sessionsAccepted,
|
|
172
|
+
eventsAccepted: coverage.eventsAccepted,
|
|
173
|
+
},
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
function persistProgress(repository, run, now) {
|
|
177
|
+
run.updatedAt = now.toISOString();
|
|
178
|
+
repository.saveBatchImportRun(run);
|
|
179
|
+
}
|
|
180
|
+
async function assertManifestSourceUnchanged(source) {
|
|
181
|
+
let details;
|
|
182
|
+
try {
|
|
183
|
+
details = await stat(source.locator);
|
|
184
|
+
}
|
|
185
|
+
catch {
|
|
186
|
+
throw new SessionContractError('SCHEMA_DRIFT', 'discovered source is no longer available; run discovery again');
|
|
187
|
+
}
|
|
188
|
+
if (details.size !== source.sizeBytes || details.mtime.toISOString() !== source.modifiedAt) {
|
|
189
|
+
throw new SessionContractError('SCHEMA_DRIFT', 'discovered source changed after manifest creation; run discovery again');
|
|
190
|
+
}
|
|
191
|
+
const fingerprint = await fingerprintSourceFile({
|
|
192
|
+
selectedPath: source.locator,
|
|
193
|
+
allowedRoots: [source.authorizedRoot],
|
|
194
|
+
});
|
|
195
|
+
if (fingerprint.digest !== source.digest || fingerprint.size !== source.sizeBytes) {
|
|
196
|
+
throw new SessionContractError('SCHEMA_DRIFT', 'discovered source content changed after manifest creation; run discovery again');
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
function adapterFor(provider) {
|
|
200
|
+
if (provider === 'claude')
|
|
201
|
+
return new ClaudeSessionAdapter();
|
|
202
|
+
if (provider === 'codex')
|
|
203
|
+
return new CodexSessionAdapter();
|
|
204
|
+
if (provider === 'cursor')
|
|
205
|
+
return new CursorSessionAdapter();
|
|
206
|
+
if (provider === 'factory')
|
|
207
|
+
return new FactorySessionAdapter();
|
|
208
|
+
throw new SessionContractError('UNSUPPORTED_OPERATION', `batch discovery import is not implemented for ${provider}`);
|
|
209
|
+
}
|
|
210
|
+
async function sessionSource(selected, adapter) {
|
|
211
|
+
const probe = await adapter.inspect(selected);
|
|
212
|
+
return SessionSourceSchema.parse({
|
|
213
|
+
contractVersion: SESSION_CONTRACT_VERSION,
|
|
214
|
+
sourceId: selected.sourceId,
|
|
215
|
+
provider: selected.provider,
|
|
216
|
+
providerProfile: providerProfile(selected.provider, selected.locatorClass),
|
|
217
|
+
locatorClass: selected.locatorClass,
|
|
218
|
+
redactedLocator: '<discovered-session-source>',
|
|
219
|
+
adapterVersion: adapterVersion(selected.provider),
|
|
220
|
+
sourceSchemaVersion: probe.sourceSchemaVersion,
|
|
221
|
+
disposition: 'implemented',
|
|
222
|
+
operationalState: probe.operationalState,
|
|
223
|
+
consistency: probe.consistency,
|
|
224
|
+
authorizedAt: new Date().toISOString(),
|
|
225
|
+
extensions: { [`native.${selected.provider}`]: {} },
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
function adapterVersion(provider) {
|
|
229
|
+
if (provider === 'claude')
|
|
230
|
+
return CLAUDE_ADAPTER_VERSION;
|
|
231
|
+
if (provider === 'codex')
|
|
232
|
+
return CODEX_ADAPTER_VERSION;
|
|
233
|
+
if (provider === 'cursor')
|
|
234
|
+
return CURSOR_ADAPTER_VERSION;
|
|
235
|
+
if (provider === 'factory')
|
|
236
|
+
return FACTORY_ADAPTER_VERSION;
|
|
237
|
+
return '1.0.0';
|
|
238
|
+
}
|
|
239
|
+
function providerProfile(provider, locatorClass) {
|
|
240
|
+
if (provider === 'claude')
|
|
241
|
+
return 'documented-local-jsonl';
|
|
242
|
+
if (provider === 'codex')
|
|
243
|
+
return 'app-server-v2-rollout-fallback';
|
|
244
|
+
if (provider === 'cursor') {
|
|
245
|
+
return locatorClass === 'cursor-agent-transcript-jsonl'
|
|
246
|
+
? 'agent-transcript-jsonl'
|
|
247
|
+
: 'cli-stream-json';
|
|
248
|
+
}
|
|
249
|
+
if (provider === 'factory')
|
|
250
|
+
return 'documented-project-jsonl';
|
|
251
|
+
return 'manual-interchange';
|
|
252
|
+
}
|
|
253
|
+
function importErrorCode(error) {
|
|
254
|
+
if (error instanceof SessionImportFailure)
|
|
255
|
+
return error.failureReceipt.errorCode;
|
|
256
|
+
if (error instanceof SessionContractError)
|
|
257
|
+
return error.code;
|
|
258
|
+
return 'IMPORT_INTERRUPTED';
|
|
259
|
+
}
|
|
260
|
+
function isAccepted(status) {
|
|
261
|
+
return status === 'committed'
|
|
262
|
+
|| status === 'duplicate'
|
|
263
|
+
|| status === 'previously-committed';
|
|
264
|
+
}
|
|
265
|
+
//# sourceMappingURL=batch-import.js.map
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { IntelligenceCandidateSchema, IntelligenceCandidateTypeSchema, SESSION_CONTRACT_VERSION, SessionContractError, sha256, } from './contracts.js';
|
|
3
|
+
export const ExtractedCandidateDraftSchema = z.object({
|
|
4
|
+
type: IntelligenceCandidateTypeSchema,
|
|
5
|
+
assertion: z.string().min(1),
|
|
6
|
+
subject: z.string().min(1).nullable(),
|
|
7
|
+
predicate: z.string().min(1).nullable(),
|
|
8
|
+
object: z.string().min(1).nullable(),
|
|
9
|
+
evidence: z.array(z.object({
|
|
10
|
+
eventId: z.string().min(1),
|
|
11
|
+
start: z.number().int().nonnegative(),
|
|
12
|
+
end: z.number().int().positive(),
|
|
13
|
+
}).strict()).min(1),
|
|
14
|
+
confidence: z.number().min(0).max(1),
|
|
15
|
+
conflictsWith: z.array(z.string().min(1)).default([]),
|
|
16
|
+
supersedes: z.array(z.string().min(1)).default([]),
|
|
17
|
+
}).strict().superRefine((candidate, context) => {
|
|
18
|
+
if (candidate.type === 'relationship'
|
|
19
|
+
&& (!candidate.subject || !candidate.predicate || !candidate.object)) {
|
|
20
|
+
context.addIssue({
|
|
21
|
+
code: z.ZodIssueCode.custom,
|
|
22
|
+
message: 'relationship candidates require subject, predicate, and object',
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
export class CandidateExtractionService {
|
|
27
|
+
store;
|
|
28
|
+
constructor(store) {
|
|
29
|
+
this.store = store;
|
|
30
|
+
}
|
|
31
|
+
async extract(input) {
|
|
32
|
+
const evidenceById = new Map(input.documents.map((document) => [document.eventId, document]));
|
|
33
|
+
const raw = await input.extractor.extract(input.documents.map((document) => Object.freeze({
|
|
34
|
+
eventId: document.eventId,
|
|
35
|
+
text: document.searchableText,
|
|
36
|
+
role: document.role,
|
|
37
|
+
})));
|
|
38
|
+
const drafts = z.array(ExtractedCandidateDraftSchema).parse(raw);
|
|
39
|
+
const candidates = drafts
|
|
40
|
+
.filter((draft) => draft.confidence >= input.policy.minimumConfidence)
|
|
41
|
+
.map((draft) => {
|
|
42
|
+
const evidence = draft.evidence.map((span) => {
|
|
43
|
+
const document = evidenceById.get(span.eventId);
|
|
44
|
+
if (!document) {
|
|
45
|
+
throw new SessionContractError('SOURCE_NOT_AUTHORIZED', 'candidate cites evidence outside the authorized extraction scope');
|
|
46
|
+
}
|
|
47
|
+
if (span.start >= span.end || span.end > document.searchableText.length) {
|
|
48
|
+
throw new SessionContractError('MALFORMED_SOURCE', 'candidate evidence span is invalid');
|
|
49
|
+
}
|
|
50
|
+
const quote = document.searchableText.slice(span.start, span.end);
|
|
51
|
+
if (!quote.trim()) {
|
|
52
|
+
throw new SessionContractError('MALFORMED_SOURCE', 'candidate evidence span must contain redacted source text');
|
|
53
|
+
}
|
|
54
|
+
return {
|
|
55
|
+
...span,
|
|
56
|
+
quoteDigest: sha256(quote),
|
|
57
|
+
quote,
|
|
58
|
+
};
|
|
59
|
+
});
|
|
60
|
+
const sensitivity = evidence.some((span) => evidenceById.get(span.eventId)?.sensitivity === 'sensitive') ? 'sensitive' : 'none';
|
|
61
|
+
if (!evidence.some((span) => evidenceSupportsAssertion(draft.assertion, span.quote))) {
|
|
62
|
+
throw new SessionContractError('MALFORMED_SOURCE', 'candidate assertion is not supported by its cited redacted evidence span');
|
|
63
|
+
}
|
|
64
|
+
const security = classifyCandidateSecurity({
|
|
65
|
+
assertion: draft.assertion,
|
|
66
|
+
subject: draft.subject,
|
|
67
|
+
predicate: draft.predicate,
|
|
68
|
+
object: draft.object,
|
|
69
|
+
});
|
|
70
|
+
return IntelligenceCandidateSchema.parse({
|
|
71
|
+
contractVersion: SESSION_CONTRACT_VERSION,
|
|
72
|
+
candidateId: stableCandidateId(draft),
|
|
73
|
+
version: 1,
|
|
74
|
+
type: draft.type,
|
|
75
|
+
assertion: draft.assertion,
|
|
76
|
+
subject: draft.subject,
|
|
77
|
+
predicate: draft.predicate,
|
|
78
|
+
object: draft.object,
|
|
79
|
+
evidence,
|
|
80
|
+
confidence: draft.confidence,
|
|
81
|
+
temporalScope: input.policy.temporalScope,
|
|
82
|
+
projectScope: input.policy.projectScope,
|
|
83
|
+
extractionMethod: input.extractor.method,
|
|
84
|
+
extractionVersion: input.extractor.version,
|
|
85
|
+
extractionPolicyVersion: input.policy.version,
|
|
86
|
+
model: input.extractor.model,
|
|
87
|
+
sensitivity,
|
|
88
|
+
security,
|
|
89
|
+
reviewState: 'pending',
|
|
90
|
+
conflictsWith: draft.conflictsWith,
|
|
91
|
+
supersedes: draft.supersedes,
|
|
92
|
+
createdAt: new Date().toISOString(),
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
return this.store.saveCandidates(candidates);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
export class StructuralCandidateExtractor {
|
|
99
|
+
method = 'structural-labels';
|
|
100
|
+
version = '1.0.0';
|
|
101
|
+
model = null;
|
|
102
|
+
extract(evidence) {
|
|
103
|
+
const drafts = [];
|
|
104
|
+
for (const item of evidence) {
|
|
105
|
+
let offset = 0;
|
|
106
|
+
for (const line of item.text.split(/\n/)) {
|
|
107
|
+
const match = STRUCTURAL_PATTERN.exec(line);
|
|
108
|
+
if (match) {
|
|
109
|
+
const label = match[1].toLowerCase();
|
|
110
|
+
const assertion = match[2].trim();
|
|
111
|
+
const start = offset + line.indexOf(assertion);
|
|
112
|
+
const type = STRUCTURAL_TYPES[label];
|
|
113
|
+
const relationship = type === 'relationship'
|
|
114
|
+
? parseRelationship(assertion)
|
|
115
|
+
: { subject: null, predicate: null, object: null };
|
|
116
|
+
if (type && (type !== 'relationship' || relationship.subject)) {
|
|
117
|
+
drafts.push({
|
|
118
|
+
type,
|
|
119
|
+
assertion,
|
|
120
|
+
...relationship,
|
|
121
|
+
evidence: [{ eventId: item.eventId, start, end: start + assertion.length }],
|
|
122
|
+
confidence: 0.8,
|
|
123
|
+
conflictsWith: [],
|
|
124
|
+
supersedes: [],
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
offset += line.length + 1;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
return drafts;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
const SECURITY_POLICY_VERSION = '1.0.0';
|
|
135
|
+
const INSTRUCTION_PATTERN = /\b(?:ignore|override|disregard)\b.{0,40}\b(?:instruction|prompt|policy|rule)s?\b|\b(?:system|developer)\s+(?:message|instruction|prompt)\b|\b(?:execute|invoke|run)\b.{0,30}\b(?:tool|command|shell|script)\b/i;
|
|
136
|
+
const SECRET_PATTERN = /\b(?:api[_-]?key|access[_-]?token|password|passwd|private[_-]?key|authorization|cookie|secret)\b\s*(?:[:=]|\bis\b)/i;
|
|
137
|
+
const STRUCTURE_PATTERN = /(?:^|\n)\s*(?:---|\.\.\.)\s*(?:\n|$)|```|~~~|<[/!?A-Za-z]|!\[[^\]]*\]\(|\[[^\]]+\]\([^)]*\)|\{\{|\{%/;
|
|
138
|
+
const CONTROL_PATTERN = /[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f-\u009f]/;
|
|
139
|
+
const BIDI_PATTERN = /[\u061c\u200e\u200f\u202a-\u202e\u2066-\u2069]/u;
|
|
140
|
+
const LATIN_PATTERN = /\p{Script=Latin}/u;
|
|
141
|
+
const CONFUSABLE_SCRIPT_PATTERN = /[\p{Script=Cyrillic}\p{Script=Greek}]/u;
|
|
142
|
+
export function classifyCandidateSecurity(input) {
|
|
143
|
+
const text = [input.assertion, input.subject, input.predicate, input.object]
|
|
144
|
+
.filter((value) => Boolean(value))
|
|
145
|
+
.join('\n');
|
|
146
|
+
const warnings = [];
|
|
147
|
+
if (INSTRUCTION_PATTERN.test(text))
|
|
148
|
+
warnings.push('instruction-like');
|
|
149
|
+
if (STRUCTURE_PATTERN.test(text))
|
|
150
|
+
warnings.push('structure-breaking');
|
|
151
|
+
if (CONTROL_PATTERN.test(text))
|
|
152
|
+
warnings.push('control-character');
|
|
153
|
+
if (BIDI_PATTERN.test(text))
|
|
154
|
+
warnings.push('bidi-control');
|
|
155
|
+
if (LATIN_PATTERN.test(text) && CONFUSABLE_SCRIPT_PATTERN.test(text)) {
|
|
156
|
+
warnings.push('unicode-confusable');
|
|
157
|
+
}
|
|
158
|
+
if (/(?:javascript|data|vbscript):|<\s*(?:script|iframe|object|embed)\b/i.test(text)) {
|
|
159
|
+
warnings.push('active-content');
|
|
160
|
+
}
|
|
161
|
+
if (SECRET_PATTERN.test(text))
|
|
162
|
+
warnings.push('secret-bearing');
|
|
163
|
+
return {
|
|
164
|
+
disposition: warnings.length === 0 ? 'clear' : 'suspicious',
|
|
165
|
+
warnings: [...new Set(warnings)],
|
|
166
|
+
requiresAcknowledgement: warnings.length > 0,
|
|
167
|
+
acknowledged: false,
|
|
168
|
+
policyVersion: SECURITY_POLICY_VERSION,
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
function evidenceSupportsAssertion(assertion, quote) {
|
|
172
|
+
const tokens = (value) => new Set(value.normalize('NFKC').toLocaleLowerCase('en-US')
|
|
173
|
+
.match(/[\p{L}\p{N}]{3,}/gu) ?? []);
|
|
174
|
+
const assertionTokens = tokens(assertion);
|
|
175
|
+
const quoteTokens = tokens(quote);
|
|
176
|
+
return [...assertionTokens].some((token) => quoteTokens.has(token));
|
|
177
|
+
}
|
|
178
|
+
const STRUCTURAL_PATTERN = /^(Decision|Requirement|Constraint|Preference|Task|Discovery|Fix|Failed approach|Procedure|Risk|Contradiction|Question|Entity|Relationship):\s*(.+)$/i;
|
|
179
|
+
const STRUCTURAL_TYPES = {
|
|
180
|
+
decision: 'decision',
|
|
181
|
+
requirement: 'requirement',
|
|
182
|
+
constraint: 'constraint',
|
|
183
|
+
preference: 'preference',
|
|
184
|
+
task: 'task',
|
|
185
|
+
discovery: 'discovery',
|
|
186
|
+
fix: 'fix',
|
|
187
|
+
'failed approach': 'failed-approach',
|
|
188
|
+
procedure: 'procedure',
|
|
189
|
+
risk: 'risk',
|
|
190
|
+
contradiction: 'contradiction',
|
|
191
|
+
question: 'question',
|
|
192
|
+
entity: 'entity',
|
|
193
|
+
relationship: 'relationship',
|
|
194
|
+
};
|
|
195
|
+
function parseRelationship(assertion) {
|
|
196
|
+
const parts = assertion.split('|').map((part) => part.trim());
|
|
197
|
+
return parts.length === 3 && parts.every(Boolean)
|
|
198
|
+
? { subject: parts[0], predicate: parts[1], object: parts[2] }
|
|
199
|
+
: { subject: null, predicate: null, object: null };
|
|
200
|
+
}
|
|
201
|
+
function stableCandidateId(draft) {
|
|
202
|
+
return sha256(JSON.stringify({
|
|
203
|
+
type: draft.type,
|
|
204
|
+
evidenceEventIds: [...new Set(draft.evidence.map((span) => span.eventId))].sort(),
|
|
205
|
+
subject: draft.subject,
|
|
206
|
+
predicate: draft.predicate,
|
|
207
|
+
object: draft.object,
|
|
208
|
+
}));
|
|
209
|
+
}
|
|
210
|
+
//# sourceMappingURL=candidates.js.map
|