@weftlet/toolkit 0.0.0-stage → 0.1.0
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/LICENSE +176 -0
- package/NOTICE +6 -0
- package/README.md +155 -2
- package/THIRD_PARTY_NOTICES.md +610 -0
- package/dist/application/agent-context.d.ts +23 -0
- package/dist/application/agent-context.js +94 -0
- package/dist/application/application.d.ts +23 -0
- package/dist/application/application.js +91 -0
- package/dist/application/hydrator.d.ts +49 -0
- package/dist/application/hydrator.js +275 -0
- package/dist/application/overlay.d.ts +6 -0
- package/dist/application/overlay.js +110 -0
- package/dist/application/saved-evidence.d.ts +38 -0
- package/dist/application/saved-evidence.js +59 -0
- package/dist/application/snapshot-checks.d.ts +16 -0
- package/dist/application/snapshot-checks.js +51 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.js +4 -0
- package/dist/configuration/cache.d.ts +2 -0
- package/dist/configuration/cache.js +10 -0
- package/dist/configuration/config.d.ts +79 -0
- package/dist/configuration/config.js +165 -0
- package/dist/core.d.ts +1 -0
- package/dist/core.js +2 -0
- package/dist/extraction/codex-auth.d.ts +12 -0
- package/dist/extraction/codex-auth.js +74 -0
- package/dist/extraction/document.d.ts +5 -0
- package/dist/extraction/document.js +54 -0
- package/dist/extraction/ontology.d.ts +32 -0
- package/dist/extraction/ontology.js +164 -0
- package/dist/extraction/providers.d.ts +8 -0
- package/dist/extraction/providers.js +58 -0
- package/dist/extraction/runtime-evidence.d.ts +2 -0
- package/dist/extraction/runtime-evidence.js +84 -0
- package/dist/extraction/semantic-citations.d.ts +7 -0
- package/dist/extraction/semantic-citations.js +27 -0
- package/dist/extraction/semantic-process.d.ts +11 -0
- package/dist/extraction/semantic-process.js +68 -0
- package/dist/extraction/semantic.d.ts +73 -0
- package/dist/extraction/semantic.js +414 -0
- package/dist/extraction/static.d.ts +52 -0
- package/dist/extraction/static.js +322 -0
- package/dist/extraction/telemetry.d.ts +19 -0
- package/dist/extraction/telemetry.js +259 -0
- package/dist/governance/governance.d.ts +60 -0
- package/dist/governance/governance.js +209 -0
- package/dist/graph/authority.d.ts +2 -0
- package/dist/graph/authority.js +13 -0
- package/dist/graph/diagnostics.d.ts +5 -0
- package/dist/graph/diagnostics.js +61 -0
- package/dist/graph/query.d.ts +16 -0
- package/dist/graph/query.js +373 -0
- package/dist/graph/rdf.d.ts +26 -0
- package/dist/graph/rdf.js +144 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +12 -0
- package/dist/infrastructure/module-resolution.d.ts +2 -0
- package/dist/infrastructure/module-resolution.js +10 -0
- package/dist/infrastructure/process.d.ts +5 -0
- package/dist/infrastructure/process.js +27 -0
- package/dist/infrastructure/util.d.ts +1 -0
- package/dist/infrastructure/util.js +2 -0
- package/dist/interfaces/cli.d.ts +4 -0
- package/dist/interfaces/cli.js +197 -0
- package/dist/interfaces/doctor.d.ts +25 -0
- package/dist/interfaces/doctor.js +49 -0
- package/dist/interfaces/init.d.ts +24 -0
- package/dist/interfaces/init.js +154 -0
- package/dist/interfaces/mcp-facade.d.ts +3 -0
- package/dist/interfaces/mcp-facade.js +4 -0
- package/dist/interfaces/mcp.d.ts +2 -0
- package/dist/interfaces/mcp.js +81 -0
- package/dist/interfaces/request.d.ts +2 -0
- package/dist/interfaces/request.js +59 -0
- package/dist/mcp.d.ts +3 -0
- package/dist/mcp.js +4 -0
- package/dist/repository/committed-tree.d.ts +2 -0
- package/dist/repository/committed-tree.js +35 -0
- package/dist/repository/git.d.ts +26 -0
- package/dist/repository/git.js +163 -0
- package/dist/repository/glob.d.ts +2 -0
- package/dist/repository/glob.js +46 -0
- package/dist/repository/working-content.d.ts +3 -0
- package/dist/repository/working-content.js +34 -0
- package/dist/storage/atomic-rename.d.ts +2 -0
- package/dist/storage/atomic-rename.js +22 -0
- package/dist/storage/storage.d.ts +15 -0
- package/dist/storage/storage.js +115 -0
- package/dist/telemetry.d.ts +1 -0
- package/dist/telemetry.js +2 -0
- package/dist/visualization/viewer-interaction.d.ts +12 -0
- package/dist/visualization/viewer-interaction.js +123 -0
- package/dist/visualization/visualization.d.ts +26 -0
- package/dist/visualization/visualization.js +491 -0
- package/docs/ADR-001-standalone-baseline.md +16 -0
- package/docs/ADR-002-saved-evidence.md +21 -0
- package/docs/ADR-003-draft-overlays.md +23 -0
- package/docs/ADR-004-rdf-query-path.md +37 -0
- package/docs/ADR-005-governance-and-optional-oml.md +37 -0
- package/docs/ADR-006-provider-boundaries.md +15 -0
- package/docs/ADR-007-toolkit-modules.md +53 -0
- package/docs/ADR-008-weftlet-packages.md +32 -0
- package/docs/CLOUD-ENVIRONMENT.md +56 -0
- package/docs/ENVIRONMENT-VALIDATION.md +78 -0
- package/docs/MIGRATING-TO-WEFTLET.md +54 -0
- package/docs/PROVIDERS.md +121 -0
- package/docs/RELEASING.md +144 -0
- package/docs/ROADMAP.md +53 -0
- package/docs/SOURCE-INVENTORY.md +48 -0
- package/docs/VALIDATION.md +490 -0
- package/docs/VIEWER.md +36 -0
- package/docs/architecture-baseline.json +57 -0
- package/docs/architecture-policy.md +41 -0
- package/docs/extraction-manifest.json +721 -0
- package/docs/licenses/REVIEW.md +16 -0
- package/docs/licenses/lru-cache-11.5.3-BlueOak.md +55 -0
- package/docs/licenses/negotiate-1.0.1-BSD.txt +22 -0
- package/docs/licenses/spark-md5-3.0.2-MIT.txt +7 -0
- package/examples/generic-typescript/context-graph.config.json +47 -0
- package/examples/generic-typescript/context-graph.oml.config.json +49 -0
- package/examples/generic-typescript/docs/search-capability.md +14 -0
- package/examples/generic-typescript/ontology/build.gradle +38 -0
- package/examples/generic-typescript/ontology/catalog.xml +5 -0
- package/examples/generic-typescript/ontology/settings.gradle +1 -0
- package/examples/generic-typescript/ontology/src/oml/context-graph.dev/core.oml +99 -0
- package/examples/generic-typescript/package.json +6 -0
- package/examples/generic-typescript/src/search.ts +11 -0
- package/examples/generic-typescript/test/search.test.ts +8 -0
- package/examples/generic-typescript/tsconfig.json +10 -0
- package/examples/pipeline/context-graph.config.json +49 -0
- package/examples/pipeline/docs/admission.md +6 -0
- package/examples/pipeline/docs/analysis.md +6 -0
- package/examples/pipeline/docs/discovery.md +6 -0
- package/examples/pipeline/docs/execution.md +6 -0
- package/examples/pipeline/docs/outcomes.md +6 -0
- package/examples/pipeline/docs/planning.md +6 -0
- package/examples/pipeline/package.json +6 -0
- package/examples/pipeline/src/admission.ts +9 -0
- package/examples/pipeline/src/analysis.ts +9 -0
- package/examples/pipeline/src/contracts/admission.ts +1 -0
- package/examples/pipeline/src/contracts/analysis.ts +1 -0
- package/examples/pipeline/src/contracts/discovery.ts +1 -0
- package/examples/pipeline/src/contracts/execution.ts +1 -0
- package/examples/pipeline/src/contracts/outcomes.ts +1 -0
- package/examples/pipeline/src/contracts/planning.ts +1 -0
- package/examples/pipeline/src/discovery.ts +9 -0
- package/examples/pipeline/src/execution.ts +9 -0
- package/examples/pipeline/src/outcomes.ts +9 -0
- package/examples/pipeline/src/planning.ts +9 -0
- package/examples/pipeline/src/queue.ts +12 -0
- package/examples/pipeline/test/pipeline.test.ts +17 -0
- package/examples/pipeline/tsconfig.json +12 -0
- package/package.json +116 -3
- package/runtime-check.d.mts +2 -0
- package/runtime-check.mjs +12 -0
- package/templates/context-graph-development/SKILL.md +37 -0
- package/third-party-tools.json +34 -0
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { execFile } from 'node:child_process';
|
|
2
|
+
import { mkdir, rm, writeFile } from 'node:fs/promises';
|
|
3
|
+
import { dirname, join, resolve } from 'node:path';
|
|
4
|
+
import { promisify } from 'node:util';
|
|
5
|
+
import { GitRepository } from "../repository/git.js";
|
|
6
|
+
import { withCommittedTree } from "../repository/committed-tree.js";
|
|
7
|
+
import { RepositoryContractExtractor, RepositoryFileExtractor, ScipSymbolExtractor, ScipTypeScriptIndexer } from "../extraction/static.js";
|
|
8
|
+
import { sha256, stableJson } from "../infrastructure/util.js";
|
|
9
|
+
import { contextPackEvidenceAuthority } from "../graph/query.js";
|
|
10
|
+
import { inWorkingScope, readWorkingContent } from "../repository/working-content.js";
|
|
11
|
+
const execute = promisify(execFile);
|
|
12
|
+
const implementationExtractors = new Set([
|
|
13
|
+
'repository-file-extractor', 'repository-contract-extractor', 'scip-symbol-extractor',
|
|
14
|
+
]);
|
|
15
|
+
/** Conservative code overlay: reindex implementation; invalidate changed prose, never synthesize intent. */
|
|
16
|
+
export async function buildWorkingOverlay(repository, config, store, storageRoot, baseSnapshotId, agent = 'default') {
|
|
17
|
+
const base = await store.read(baseSnapshotId);
|
|
18
|
+
if (!base || base.manifest.overlayDigest)
|
|
19
|
+
throw new Error('Overlay requires a committed base snapshot');
|
|
20
|
+
if (base.manifest.configurationDigest !== sha256(stableJson(config))) {
|
|
21
|
+
throw new Error('Overlay configuration must match its base snapshot');
|
|
22
|
+
}
|
|
23
|
+
const head = await repository.resolveRef('HEAD');
|
|
24
|
+
if (head !== base.manifest.commitSha)
|
|
25
|
+
throw new Error('Overlay base must match checkout HEAD');
|
|
26
|
+
const changed = await repository.workingChanges();
|
|
27
|
+
if (changed.length > 500)
|
|
28
|
+
throw new Error('Overlay exceeds the 500 changed-path bound');
|
|
29
|
+
const captured = [];
|
|
30
|
+
const roots = [...config.codeRoots, ...config.semanticSources, ...config.evidenceRoots,
|
|
31
|
+
...config.contractPatterns];
|
|
32
|
+
const excluded = [];
|
|
33
|
+
for (const path of changed) {
|
|
34
|
+
if (!inWorkingScope(path, roots)) {
|
|
35
|
+
excluded.push(path);
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
const content = await readWorkingContent(repository.root, path);
|
|
39
|
+
captured.push({ path, content });
|
|
40
|
+
}
|
|
41
|
+
const overlayCapture = {
|
|
42
|
+
files: captured.map(({ path, content }) => ({ path, digest: content === null ? null : sha256(content) })),
|
|
43
|
+
sourcePatterns: roots, excludedPaths: excluded,
|
|
44
|
+
};
|
|
45
|
+
const overlayDigest = sha256(stableJson({ version: 2, ...overlayCapture }));
|
|
46
|
+
const snapshotId = `${head}-overlay-${sha256(base.manifest.contentDigest + overlayDigest).slice(0, 32)}`;
|
|
47
|
+
const affected = new Set(captured.map((file) => file.path));
|
|
48
|
+
const facts = await withCommittedTree(repository.root, head, async (root) => {
|
|
49
|
+
for (const file of captured) {
|
|
50
|
+
const target = join(root, file.path);
|
|
51
|
+
if (file.content === null)
|
|
52
|
+
await rm(target, { force: true });
|
|
53
|
+
else {
|
|
54
|
+
await mkdir(dirname(target), { recursive: true });
|
|
55
|
+
await writeFile(target, file.content);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
const git = (...args) => execute('git', args, { cwd: root });
|
|
59
|
+
await git('init', '--quiet');
|
|
60
|
+
await git('add', '.');
|
|
61
|
+
await git('-c', 'user.name=Context Graph Draft', '-c', 'user.email=draft@example.invalid', '-c', 'commit.gpgsign=false', 'commit', '--quiet', '-m', 'Isolated overlay input');
|
|
62
|
+
const draftRepository = new GitRepository(root);
|
|
63
|
+
const context = {
|
|
64
|
+
repositoryRoot: root, repositoryId: config.repositoryId, ref: 'HEAD',
|
|
65
|
+
commitSha: await draftRepository.resolveRef('HEAD'), snapshotId,
|
|
66
|
+
changedPaths: captured.filter((f) => f.content !== null).map((f) => f.path),
|
|
67
|
+
deletedPaths: captured.filter((f) => f.content === null).map((f) => f.path),
|
|
68
|
+
renamedPaths: {},
|
|
69
|
+
};
|
|
70
|
+
const scip = await new ScipTypeScriptIndexer(config.scip, storageRoot).index(context);
|
|
71
|
+
const extractors = [
|
|
72
|
+
new RepositoryFileExtractor(draftRepository, config.codeRoots, scip.digest),
|
|
73
|
+
new RepositoryContractExtractor(draftRepository, config.contractPatterns, config.contractConsumerRoots ?? config.codeRoots, config.contractAliases),
|
|
74
|
+
new ScipSymbolExtractor(draftRepository, scip),
|
|
75
|
+
];
|
|
76
|
+
return (await Promise.all(extractors.map((e) => e.extract(context)))).flat().map(({ id: _id, ...fact }) => {
|
|
77
|
+
const draft = { ...fact, snapshotId, authority: 'draft',
|
|
78
|
+
provenance: { ...fact.provenance, commitSha: head } };
|
|
79
|
+
return { ...draft, id: sha256(stableJson(draft)) };
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
const retained = base.facts.filter((fact) => !implementationExtractors.has(fact.extractor.id) &&
|
|
83
|
+
contextPackEvidenceAuthority(fact) !== 'implemented' &&
|
|
84
|
+
fact.extractor.id !== 'otlp-runtime-evidence' &&
|
|
85
|
+
!affected.has(fact.provenance.path) &&
|
|
86
|
+
![fact.subject, fact.object].some((node) => [...affected].some((path) => node.id === `component:file:${path}` || node.id === `document:${path}` || node.label === path))).map((fact) => ({ ...fact, snapshotId }));
|
|
87
|
+
const allFacts = [...retained, ...facts].sort((a, b) => a.id.localeCompare(b.id));
|
|
88
|
+
const overlay = {
|
|
89
|
+
...base,
|
|
90
|
+
manifest: {
|
|
91
|
+
...base.manifest, snapshotId, baseSha: head, overlayDigest, overlayCapture,
|
|
92
|
+
generatedAt: new Date().toISOString(), contentDigest: sha256(stableJson(allFacts)),
|
|
93
|
+
},
|
|
94
|
+
facts: allFacts,
|
|
95
|
+
invariants: base.invariants.map((rule) => ({
|
|
96
|
+
...rule, status: 'unknown', message: `Draft overlay requires revalidation: ${rule.invariantId}`,
|
|
97
|
+
})),
|
|
98
|
+
runtimeEvidence: [],
|
|
99
|
+
unresolved: [
|
|
100
|
+
'Draft implementation overlay: changed semantic documents require explicit re-extraction.',
|
|
101
|
+
`Changes outside configured source scope are excluded: ${excluded.join(', ') || '(none)'}`,
|
|
102
|
+
],
|
|
103
|
+
diagnostics: [],
|
|
104
|
+
};
|
|
105
|
+
if (await repository.resolveRef('HEAD') !== head)
|
|
106
|
+
throw new Error('HEAD changed during overlay capture; retry');
|
|
107
|
+
await store.write(overlay);
|
|
108
|
+
await store.updateHead(`overlay-${sha256(`${resolve(repository.root)}:${agent}`).slice(0, 24)}`, snapshotId);
|
|
109
|
+
return overlay;
|
|
110
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { ImpactQueryService, ImpactScanRequest } from "@weftlet/core";
|
|
2
|
+
import type { GitRepository } from "../repository/git.js";
|
|
3
|
+
/** Read-side metadata; never calls an indexer, model, compiler, or refresh operation. */
|
|
4
|
+
export declare class SavedEvidenceQueryService implements ImpactQueryService {
|
|
5
|
+
private readonly queries;
|
|
6
|
+
private readonly repository;
|
|
7
|
+
private readonly configurationDigest;
|
|
8
|
+
constructor(queries: ImpactQueryService, repository: GitRepository, configurationDigest: string);
|
|
9
|
+
scan(request: ImpactScanRequest): Promise<{
|
|
10
|
+
markdown: string;
|
|
11
|
+
evidenceState: {
|
|
12
|
+
stale: boolean;
|
|
13
|
+
configurationMatches: boolean;
|
|
14
|
+
eligibleForCurrentDiffValidation: false;
|
|
15
|
+
overlayFreshness?: "current" | "stale" | "unknown" | undefined;
|
|
16
|
+
capturedWorkingChanges?: string[] | undefined;
|
|
17
|
+
changedSinceCapture?: string[] | undefined;
|
|
18
|
+
currentCommit: string | undefined;
|
|
19
|
+
excludedWorkingChanges: string[];
|
|
20
|
+
};
|
|
21
|
+
snapshot: Pick<import("@weftlet/core").SnapshotManifest, "snapshotId" | "commitSha" | "contentDigest" | "configurationDigest" | "scope" | "baseSha" | "overlayDigest" | "overlayCapture">;
|
|
22
|
+
capabilities: readonly import("@weftlet/core").GraphNode[];
|
|
23
|
+
facts: readonly import("@weftlet/core").GraphFact[];
|
|
24
|
+
invariants: readonly import("@weftlet/core").InvariantResult[];
|
|
25
|
+
runtimeEvidence: readonly import("@weftlet/core").RuntimeTraceReference[];
|
|
26
|
+
suggestedFiles: readonly string[];
|
|
27
|
+
coverage: import("@weftlet/core").ContextPackCoverage;
|
|
28
|
+
diagnostics: import("@weftlet/core").DiagnosticReport;
|
|
29
|
+
exploration?: {
|
|
30
|
+
readonly returned: number;
|
|
31
|
+
readonly reachableFacts: number;
|
|
32
|
+
readonly maxDepth: number;
|
|
33
|
+
readonly depthLimitReached: boolean;
|
|
34
|
+
readonly truncated: boolean;
|
|
35
|
+
readonly continuation?: string;
|
|
36
|
+
};
|
|
37
|
+
}>;
|
|
38
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { MarkdownContextPackRenderer } from "../graph/query.js";
|
|
2
|
+
import { inWorkingScope, readWorkingContent } from "../repository/working-content.js";
|
|
3
|
+
import { sha256 } from "../infrastructure/util.js";
|
|
4
|
+
/** Read-side metadata; never calls an indexer, model, compiler, or refresh operation. */
|
|
5
|
+
export class SavedEvidenceQueryService {
|
|
6
|
+
queries;
|
|
7
|
+
repository;
|
|
8
|
+
configurationDigest;
|
|
9
|
+
constructor(queries, repository, configurationDigest) {
|
|
10
|
+
this.queries = queries;
|
|
11
|
+
this.repository = repository;
|
|
12
|
+
this.configurationDigest = configurationDigest;
|
|
13
|
+
}
|
|
14
|
+
async scan(request) {
|
|
15
|
+
const pack = await this.queries.scan(request);
|
|
16
|
+
const currentCommit = await this.repository.resolveRef('HEAD').catch(() => undefined);
|
|
17
|
+
const workingChanges = await this.repository.workingChanges();
|
|
18
|
+
const capturedWorkingChanges = [];
|
|
19
|
+
const changedSinceCapture = new Set();
|
|
20
|
+
let overlayFreshness;
|
|
21
|
+
if (pack.snapshot.overlayDigest) {
|
|
22
|
+
overlayFreshness = 'unknown';
|
|
23
|
+
const capture = pack.snapshot.overlayCapture;
|
|
24
|
+
if (capture) {
|
|
25
|
+
overlayFreshness = 'current';
|
|
26
|
+
for (const file of capture.files) {
|
|
27
|
+
try {
|
|
28
|
+
const content = await readWorkingContent(this.repository.root, file.path);
|
|
29
|
+
if ((content === null ? null : sha256(content)) === file.digest)
|
|
30
|
+
capturedWorkingChanges.push(file.path);
|
|
31
|
+
else
|
|
32
|
+
changedSinceCapture.add(file.path);
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
overlayFreshness = 'unknown';
|
|
36
|
+
changedSinceCapture.add(file.path);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
for (const path of workingChanges) {
|
|
40
|
+
if (!capturedWorkingChanges.includes(path) && inWorkingScope(path, capture.sourcePatterns))
|
|
41
|
+
changedSinceCapture.add(path);
|
|
42
|
+
}
|
|
43
|
+
if (changedSinceCapture.size && overlayFreshness !== 'unknown')
|
|
44
|
+
overlayFreshness = 'stale';
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
const excludedWorkingChanges = workingChanges.filter((path) => !capturedWorkingChanges.includes(path));
|
|
48
|
+
const evidenceState = {
|
|
49
|
+
currentCommit,
|
|
50
|
+
excludedWorkingChanges,
|
|
51
|
+
...(overlayFreshness ? { overlayFreshness, capturedWorkingChanges, changedSinceCapture: [...changedSinceCapture].sort() } : {}),
|
|
52
|
+
stale: currentCommit !== pack.snapshot.commitSha || (overlayFreshness !== undefined && overlayFreshness !== 'current'),
|
|
53
|
+
configurationMatches: pack.snapshot.configurationDigest === this.configurationDigest,
|
|
54
|
+
eligibleForCurrentDiffValidation: false,
|
|
55
|
+
};
|
|
56
|
+
const result = { ...pack, evidenceState };
|
|
57
|
+
return { ...result, markdown: new MarkdownContextPackRenderer().render(result) };
|
|
58
|
+
}
|
|
59
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { AgentSemanticScopeResolver } from "./agent-context.js";
|
|
2
|
+
import type { ImpactScanRequest, SnapshotStore } from "@weftlet/core";
|
|
3
|
+
import type { GitRepository } from "../repository/git.js";
|
|
4
|
+
import type { ContextGraphHydrator } from "./hydrator.js";
|
|
5
|
+
export declare class SnapshotChecks {
|
|
6
|
+
private readonly store;
|
|
7
|
+
private readonly repository;
|
|
8
|
+
private readonly hydrator;
|
|
9
|
+
private readonly scopeResolver?;
|
|
10
|
+
private readonly configurationDigest?;
|
|
11
|
+
constructor(store: Pick<SnapshotStore, 'readHead'>, repository: Pick<GitRepository, 'resolveRef'> & Partial<Pick<GitRepository, 'workingChanges'>>, hydrator: Pick<ContextGraphHydrator, 'compareClean'>, scopeResolver?: Pick<AgentSemanticScopeResolver, "resolve"> | undefined, configurationDigest?: string | undefined);
|
|
12
|
+
private target;
|
|
13
|
+
private readTarget;
|
|
14
|
+
read(request: ImpactScanRequest, ref?: string): Promise<import("@weftlet/core").GraphSnapshot>;
|
|
15
|
+
compare(request: ImpactScanRequest, ref?: string): Promise<import("./hydrator.js").HydrationResult>;
|
|
16
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
// Final checks must use the same scope resolver as prepared impact scans, without silently
|
|
2
|
+
// starting hydration or substituting another owner's (or an older commit's) evidence.
|
|
3
|
+
export class SnapshotChecks {
|
|
4
|
+
store;
|
|
5
|
+
repository;
|
|
6
|
+
hydrator;
|
|
7
|
+
scopeResolver;
|
|
8
|
+
configurationDigest;
|
|
9
|
+
constructor(store, repository, hydrator, scopeResolver, configurationDigest) {
|
|
10
|
+
this.store = store;
|
|
11
|
+
this.repository = repository;
|
|
12
|
+
this.hydrator = hydrator;
|
|
13
|
+
this.scopeResolver = scopeResolver;
|
|
14
|
+
this.configurationDigest = configurationDigest;
|
|
15
|
+
}
|
|
16
|
+
async target(request, ref) {
|
|
17
|
+
if (request.snapshotId)
|
|
18
|
+
throw new Error('Final checks select exact-ref evidence, not --snapshot');
|
|
19
|
+
const commitSha = await this.repository.resolveRef(ref);
|
|
20
|
+
if ((await this.repository.workingChanges?.())?.length) {
|
|
21
|
+
throw new Error('Saved committed evidence cannot validate excluded working changes');
|
|
22
|
+
}
|
|
23
|
+
const scope = await this.scopeResolver?.resolve(request);
|
|
24
|
+
if (scope && scope.commitSha !== commitSha) {
|
|
25
|
+
throw new Error('Agent final checks require the current HEAD; the requested ref differs');
|
|
26
|
+
}
|
|
27
|
+
return {
|
|
28
|
+
ref: commitSha,
|
|
29
|
+
headName: scope?.headName ?? 'default',
|
|
30
|
+
semanticSources: scope?.semanticSources,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
async readTarget(target) {
|
|
34
|
+
const snapshot = await this.store.readHead(target.headName);
|
|
35
|
+
if (!snapshot || snapshot.manifest.commitSha !== target.ref || snapshot.manifest.overlayDigest) {
|
|
36
|
+
throw new Error(`No exact-commit graph snapshot for scope ${target.headName} at ${target.ref}. Run hydrate with the same config and owner/task selectors first.`);
|
|
37
|
+
}
|
|
38
|
+
if (this.configurationDigest && snapshot.manifest.configurationDigest !== this.configurationDigest) {
|
|
39
|
+
throw new Error('Snapshot configuration differs from current configuration; refresh required');
|
|
40
|
+
}
|
|
41
|
+
return snapshot;
|
|
42
|
+
}
|
|
43
|
+
async read(request, ref = 'HEAD') {
|
|
44
|
+
return this.readTarget(await this.target(request, ref));
|
|
45
|
+
}
|
|
46
|
+
async compare(request, ref = 'HEAD') {
|
|
47
|
+
const target = await this.target(request, ref);
|
|
48
|
+
await this.readTarget(target);
|
|
49
|
+
return this.hydrator.compareClean(target);
|
|
50
|
+
}
|
|
51
|
+
}
|
package/dist/cli.d.ts
ADDED
package/dist/cli.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// Modified for standalone extraction; see docs/extraction-manifest.json.
|
|
2
|
+
import { homedir } from 'node:os';
|
|
3
|
+
import { resolve } from 'node:path';
|
|
4
|
+
import { sha256, stableJson } from "../infrastructure/util.js";
|
|
5
|
+
export function resolveStorageRoot(repositoryRoot, config, userCacheRoot = resolve(homedir(), '.cache', 'context-graph')) {
|
|
6
|
+
if (config.storageScope !== 'user')
|
|
7
|
+
return resolve(repositoryRoot, config.storagePath);
|
|
8
|
+
const profile = sha256(stableJson({ config, checkout: resolve(repositoryRoot) })).slice(0, 20);
|
|
9
|
+
return resolve(userCacheRoot, profile);
|
|
10
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
export interface ContextGraphConfig {
|
|
2
|
+
readonly schemaVersion: 1;
|
|
3
|
+
readonly repositoryRoot?: string;
|
|
4
|
+
readonly repositoryId: string;
|
|
5
|
+
readonly namespace: string;
|
|
6
|
+
readonly adapter?: string;
|
|
7
|
+
readonly semanticSources: readonly string[];
|
|
8
|
+
/** Explicit owner-approved source paths/globs; other model-derived prose remains inferred. */
|
|
9
|
+
readonly approvedIntentSources?: readonly string[];
|
|
10
|
+
readonly codeRoots: readonly string[];
|
|
11
|
+
readonly contractPatterns: readonly string[];
|
|
12
|
+
readonly contractConsumerRoots?: readonly string[];
|
|
13
|
+
readonly contractAliases?: Readonly<Record<string, readonly string[]>>;
|
|
14
|
+
readonly evidenceRoots: readonly string[];
|
|
15
|
+
readonly semantic: {
|
|
16
|
+
readonly provider: 'codex-cli' | 'module';
|
|
17
|
+
/** Codex authentication only; secrets stay in the process environment. */
|
|
18
|
+
readonly auth?: {
|
|
19
|
+
readonly mode: 'chatgpt';
|
|
20
|
+
} | {
|
|
21
|
+
readonly mode: 'api-key';
|
|
22
|
+
readonly apiKeyEnv?: string;
|
|
23
|
+
};
|
|
24
|
+
readonly providerModule?: string;
|
|
25
|
+
readonly providerIdentity?: string;
|
|
26
|
+
readonly timeoutMs?: number;
|
|
27
|
+
readonly retries?: number;
|
|
28
|
+
readonly maxDocuments?: number;
|
|
29
|
+
readonly maxSourceBytes?: number;
|
|
30
|
+
readonly model: string;
|
|
31
|
+
readonly reasoningEffort: string;
|
|
32
|
+
readonly promptVersion: string;
|
|
33
|
+
readonly extractorVersion: string;
|
|
34
|
+
readonly adapterVersion: string;
|
|
35
|
+
readonly ontologyVersion: string;
|
|
36
|
+
readonly minimumConfidence: number;
|
|
37
|
+
readonly failOnUnresolved: boolean;
|
|
38
|
+
readonly concurrency: number;
|
|
39
|
+
};
|
|
40
|
+
readonly scip: {
|
|
41
|
+
readonly version: string;
|
|
42
|
+
readonly command: string;
|
|
43
|
+
readonly args: readonly string[];
|
|
44
|
+
};
|
|
45
|
+
readonly ontology: {
|
|
46
|
+
readonly backend?: 'rdf' | 'opencaesar';
|
|
47
|
+
readonly toolVersion: string;
|
|
48
|
+
readonly containerImage?: string;
|
|
49
|
+
readonly projectPath?: string;
|
|
50
|
+
readonly usedVocabularyIris?: readonly string[];
|
|
51
|
+
readonly includedDescriptionIris?: readonly string[];
|
|
52
|
+
};
|
|
53
|
+
readonly runtimeEvidence: readonly string[];
|
|
54
|
+
readonly runtimeEvidencePolicy?: {
|
|
55
|
+
readonly retentionDays: number;
|
|
56
|
+
readonly maxReferences: number;
|
|
57
|
+
readonly maxSourceBytes: number;
|
|
58
|
+
readonly requireEnvironment: boolean;
|
|
59
|
+
readonly requireServiceInstance: boolean;
|
|
60
|
+
readonly allowedServiceNames?: readonly string[];
|
|
61
|
+
readonly allowedEnvironments?: readonly string[];
|
|
62
|
+
readonly capabilityKeys: readonly string[];
|
|
63
|
+
readonly operationKeys: readonly string[];
|
|
64
|
+
};
|
|
65
|
+
readonly storagePath: string;
|
|
66
|
+
readonly storageScope?: 'workspace' | 'user';
|
|
67
|
+
readonly agentContext?: {
|
|
68
|
+
readonly alwaysSemanticSources: readonly string[];
|
|
69
|
+
readonly followDocumentReferences: boolean;
|
|
70
|
+
readonly includeMatchingEvidence: boolean;
|
|
71
|
+
readonly maxReferencedDocuments: number;
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
export interface LoadedConfig {
|
|
75
|
+
readonly config: ContextGraphConfig;
|
|
76
|
+
readonly configPath: string;
|
|
77
|
+
readonly repositoryRoot: string;
|
|
78
|
+
}
|
|
79
|
+
export declare function loadConfig(configPath: string, initialDirectory?: string | undefined): Promise<LoadedConfig>;
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
// Modified for standalone extraction; see docs/extraction-manifest.json.
|
|
2
|
+
import { existsSync } from 'node:fs';
|
|
3
|
+
import { readFile } from 'node:fs/promises';
|
|
4
|
+
import { dirname, resolve } from 'node:path';
|
|
5
|
+
function isRecord(value) {
|
|
6
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
7
|
+
}
|
|
8
|
+
function isNonEmptyString(value) {
|
|
9
|
+
return typeof value === 'string' && value.trim().length > 0;
|
|
10
|
+
}
|
|
11
|
+
function isStringArray(value) {
|
|
12
|
+
return Array.isArray(value) && value.every((item) => typeof item === 'string');
|
|
13
|
+
}
|
|
14
|
+
function requireConfig(condition, path, absolutePath) {
|
|
15
|
+
if (!condition) {
|
|
16
|
+
throw new Error(`Invalid context graph config (${absolutePath}): ${path}`);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
function validateConfig(value, absolutePath) {
|
|
20
|
+
requireConfig(isRecord(value), 'root must be an object', absolutePath);
|
|
21
|
+
if (!isRecord(value))
|
|
22
|
+
return;
|
|
23
|
+
requireConfig(value.schemaVersion === 1, 'schemaVersion must be 1', absolutePath);
|
|
24
|
+
for (const field of ['repositoryId', 'namespace', 'storagePath']) {
|
|
25
|
+
requireConfig(isNonEmptyString(value[field]), `${field} must be a non-empty string`, absolutePath);
|
|
26
|
+
}
|
|
27
|
+
if (value.repositoryRoot !== undefined) {
|
|
28
|
+
requireConfig(isNonEmptyString(value.repositoryRoot), 'repositoryRoot must be a non-empty string when provided', absolutePath);
|
|
29
|
+
}
|
|
30
|
+
if (value.adapter !== undefined) {
|
|
31
|
+
requireConfig(isNonEmptyString(value.adapter), 'adapter must be a non-empty string when provided', absolutePath);
|
|
32
|
+
}
|
|
33
|
+
if (value.approvedIntentSources !== undefined) {
|
|
34
|
+
requireConfig(isStringArray(value.approvedIntentSources), 'approvedIntentSources must be an array of strings', absolutePath);
|
|
35
|
+
}
|
|
36
|
+
for (const field of [
|
|
37
|
+
'semanticSources',
|
|
38
|
+
'codeRoots',
|
|
39
|
+
'contractPatterns',
|
|
40
|
+
'evidenceRoots',
|
|
41
|
+
'runtimeEvidence',
|
|
42
|
+
]) {
|
|
43
|
+
requireConfig(isStringArray(value[field]), `${field} must be an array of strings`, absolutePath);
|
|
44
|
+
}
|
|
45
|
+
if (value.contractConsumerRoots !== undefined) {
|
|
46
|
+
requireConfig(isStringArray(value.contractConsumerRoots), 'contractConsumerRoots must be an array of strings when provided', absolutePath);
|
|
47
|
+
}
|
|
48
|
+
if (value.contractAliases !== undefined) {
|
|
49
|
+
requireConfig(isRecord(value.contractAliases), 'contractAliases must be an object when provided', absolutePath);
|
|
50
|
+
if (isRecord(value.contractAliases)) {
|
|
51
|
+
for (const [path, aliases] of Object.entries(value.contractAliases)) {
|
|
52
|
+
requireConfig(isNonEmptyString(path) && isStringArray(aliases) && aliases.length > 0, `contractAliases.${path} must be a non-empty array of strings`, absolutePath);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
if (value.runtimeEvidencePolicy !== undefined) {
|
|
57
|
+
requireConfig(isRecord(value.runtimeEvidencePolicy), 'runtimeEvidencePolicy must be an object', absolutePath);
|
|
58
|
+
if (isRecord(value.runtimeEvidencePolicy)) {
|
|
59
|
+
for (const field of ['retentionDays', 'maxReferences', 'maxSourceBytes']) {
|
|
60
|
+
requireConfig(Number.isInteger(value.runtimeEvidencePolicy[field]) &&
|
|
61
|
+
Number(value.runtimeEvidencePolicy[field]) > 0, `runtimeEvidencePolicy.${field} must be a positive integer`, absolutePath);
|
|
62
|
+
}
|
|
63
|
+
for (const field of ['requireEnvironment', 'requireServiceInstance']) {
|
|
64
|
+
requireConfig(typeof value.runtimeEvidencePolicy[field] === 'boolean', `runtimeEvidencePolicy.${field} must be a boolean`, absolutePath);
|
|
65
|
+
}
|
|
66
|
+
for (const field of [
|
|
67
|
+
'allowedServiceNames',
|
|
68
|
+
'allowedEnvironments',
|
|
69
|
+
'capabilityKeys',
|
|
70
|
+
'operationKeys',
|
|
71
|
+
]) {
|
|
72
|
+
if (value.runtimeEvidencePolicy[field] === undefined && field.startsWith('allowed')) {
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
requireConfig(isStringArray(value.runtimeEvidencePolicy[field]) &&
|
|
76
|
+
value.runtimeEvidencePolicy[field].length > 0, `runtimeEvidencePolicy.${field} must be a non-empty array of strings`, absolutePath);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
if (value.storageScope !== undefined) {
|
|
81
|
+
requireConfig(value.storageScope === 'workspace' || value.storageScope === 'user', 'storageScope must be workspace or user when provided', absolutePath);
|
|
82
|
+
}
|
|
83
|
+
if (value.agentContext !== undefined) {
|
|
84
|
+
requireConfig(isRecord(value.agentContext), 'agentContext must be an object', absolutePath);
|
|
85
|
+
if (isRecord(value.agentContext)) {
|
|
86
|
+
requireConfig(isStringArray(value.agentContext.alwaysSemanticSources), 'agentContext.alwaysSemanticSources must be an array of strings', absolutePath);
|
|
87
|
+
requireConfig(typeof value.agentContext.followDocumentReferences === 'boolean', 'agentContext.followDocumentReferences must be a boolean', absolutePath);
|
|
88
|
+
requireConfig(typeof value.agentContext.includeMatchingEvidence === 'boolean', 'agentContext.includeMatchingEvidence must be a boolean', absolutePath);
|
|
89
|
+
requireConfig(Number.isInteger(value.agentContext.maxReferencedDocuments) &&
|
|
90
|
+
Number(value.agentContext.maxReferencedDocuments) > 0, 'agentContext.maxReferencedDocuments must be a positive integer', absolutePath);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
requireConfig(isRecord(value.semantic), 'semantic must be an object', absolutePath);
|
|
94
|
+
if (isRecord(value.semantic)) {
|
|
95
|
+
requireConfig(value.semantic.provider === 'codex-cli' || value.semantic.provider === 'module', 'semantic.provider is invalid', absolutePath);
|
|
96
|
+
if (value.semantic.provider === 'module') {
|
|
97
|
+
requireConfig(isNonEmptyString(value.semantic.providerModule) && isNonEmptyString(value.semantic.providerIdentity), 'module providers require providerModule and providerIdentity', absolutePath);
|
|
98
|
+
}
|
|
99
|
+
if (value.semantic.auth !== undefined) {
|
|
100
|
+
const auth = value.semantic.auth;
|
|
101
|
+
requireConfig(value.semantic.provider === 'codex-cli' && isRecord(auth), 'semantic.auth must be an object for codex-cli only', absolutePath);
|
|
102
|
+
if (isRecord(auth)) {
|
|
103
|
+
requireConfig(auth.mode === 'chatgpt' || auth.mode === 'api-key', 'semantic.auth.mode must be chatgpt or api-key', absolutePath);
|
|
104
|
+
requireConfig(Object.keys(auth).every(key => key === 'mode' || (auth.mode === 'api-key' && key === 'apiKeyEnv')), 'semantic.auth accepts only mode and, for api-key, apiKeyEnv; never store credentials here', absolutePath);
|
|
105
|
+
requireConfig(auth.apiKeyEnv === undefined || (typeof auth.apiKeyEnv === 'string' && /^[A-Za-z_][A-Za-z0-9_]*$/.test(auth.apiKeyEnv)), 'semantic.auth.apiKeyEnv must name an environment variable', absolutePath);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
for (const [field, max] of [['timeoutMs', 300_000], ['retries', 2], ['maxDocuments', 1000], ['maxSourceBytes', 1_048_576]]) {
|
|
109
|
+
const item = value.semantic[field];
|
|
110
|
+
if (item !== undefined)
|
|
111
|
+
requireConfig(Number.isInteger(item) && Number(item) >= (field === 'retries' ? 0 : 1) && Number(item) <= max, `semantic.${field} exceeds supported bounds`, absolutePath);
|
|
112
|
+
}
|
|
113
|
+
for (const field of [
|
|
114
|
+
'model',
|
|
115
|
+
'reasoningEffort',
|
|
116
|
+
'promptVersion',
|
|
117
|
+
'extractorVersion',
|
|
118
|
+
'adapterVersion',
|
|
119
|
+
'ontologyVersion',
|
|
120
|
+
]) {
|
|
121
|
+
requireConfig(isNonEmptyString(value.semantic[field]), `semantic.${field} must be a non-empty string`, absolutePath);
|
|
122
|
+
}
|
|
123
|
+
requireConfig(typeof value.semantic.minimumConfidence === 'number' &&
|
|
124
|
+
value.semantic.minimumConfidence >= 0 &&
|
|
125
|
+
value.semantic.minimumConfidence <= 1, 'semantic.minimumConfidence must be between 0 and 1', absolutePath);
|
|
126
|
+
requireConfig(typeof value.semantic.failOnUnresolved === 'boolean', 'semantic.failOnUnresolved must be a boolean', absolutePath);
|
|
127
|
+
requireConfig(Number.isInteger(value.semantic.concurrency) && Number(value.semantic.concurrency) > 0 && Number(value.semantic.concurrency) <= 4, 'semantic.concurrency must be a positive integer no greater than 4', absolutePath);
|
|
128
|
+
}
|
|
129
|
+
requireConfig(isRecord(value.scip), 'scip must be an object', absolutePath);
|
|
130
|
+
if (isRecord(value.scip)) {
|
|
131
|
+
requireConfig(isNonEmptyString(value.scip.version), 'scip.version is required', absolutePath);
|
|
132
|
+
requireConfig(isNonEmptyString(value.scip.command), 'scip.command is required', absolutePath);
|
|
133
|
+
requireConfig(isStringArray(value.scip.args), 'scip.args must be an array of strings', absolutePath);
|
|
134
|
+
}
|
|
135
|
+
requireConfig(isRecord(value.ontology), 'ontology must be an object', absolutePath);
|
|
136
|
+
if (isRecord(value.ontology)) {
|
|
137
|
+
requireConfig(value.ontology.backend === undefined || ['rdf', 'opencaesar'].includes(String(value.ontology.backend)), 'ontology.backend must be rdf or opencaesar', absolutePath);
|
|
138
|
+
for (const field of ['toolVersion']) {
|
|
139
|
+
requireConfig(isNonEmptyString(value.ontology[field]), `ontology.${field} must be a non-empty string`, absolutePath);
|
|
140
|
+
}
|
|
141
|
+
for (const field of ['containerImage', 'projectPath']) {
|
|
142
|
+
if (value.ontology.backend === 'opencaesar' || value.ontology[field] !== undefined) {
|
|
143
|
+
requireConfig(isNonEmptyString(value.ontology[field]), `ontology.${field} must be a non-empty string`, absolutePath);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
for (const field of ['usedVocabularyIris', 'includedDescriptionIris']) {
|
|
147
|
+
if (value.ontology[field] !== undefined) {
|
|
148
|
+
requireConfig(isStringArray(value.ontology[field]), `ontology.${field} must be an array of strings when provided`, absolutePath);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
export async function loadConfig(configPath, initialDirectory = process.env.INIT_CWD) {
|
|
154
|
+
const directPath = resolve(configPath);
|
|
155
|
+
const initialPath = initialDirectory ? resolve(initialDirectory, configPath) : undefined;
|
|
156
|
+
const absolutePath = existsSync(directPath) || !initialPath || !existsSync(initialPath) ? directPath : initialPath;
|
|
157
|
+
const parsed = JSON.parse(await readFile(absolutePath, 'utf8'));
|
|
158
|
+
validateConfig(parsed, absolutePath);
|
|
159
|
+
const repositoryRoot = resolve(dirname(absolutePath), parsed.repositoryRoot ?? '.');
|
|
160
|
+
return {
|
|
161
|
+
config: parsed,
|
|
162
|
+
configPath: absolutePath,
|
|
163
|
+
repositoryRoot,
|
|
164
|
+
};
|
|
165
|
+
}
|
package/dist/core.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from '@weftlet/core';
|
package/dist/core.js
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { ContextGraphConfig } from '../configuration/config.js';
|
|
2
|
+
import { type SemanticRunOptions } from './semantic-process.js';
|
|
3
|
+
export type SemanticCommandEnvironment = Record<string, string | undefined>;
|
|
4
|
+
export declare function semanticCommandEnvironment(source: Readonly<Record<string, string | undefined>>): SemanticCommandEnvironment;
|
|
5
|
+
export type CodexLoginMode = 'chatgpt' | 'api-key' | 'unknown' | 'unavailable';
|
|
6
|
+
export type CodexLoginProbe = (command: string, environment: SemanticCommandEnvironment, options?: SemanticRunOptions) => Promise<CodexLoginMode>;
|
|
7
|
+
/** Read-only status check. Never expose output: API login status may contain key fragments. */
|
|
8
|
+
export declare const readCodexLoginMode: CodexLoginProbe;
|
|
9
|
+
type CodexAuth = ContextGraphConfig['semantic']['auth'];
|
|
10
|
+
/** No auth mutation or fallback. A missing key must not fall back to a saved account. */
|
|
11
|
+
export declare function prepareCodexAuthentication(auth: CodexAuth, source: Readonly<Record<string, string | undefined>>, command: string, options?: SemanticRunOptions, probe?: CodexLoginProbe): Promise<SemanticCommandEnvironment>;
|
|
12
|
+
export {};
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
// Environment allowlist adapted from the extracted semantic.ts; see docs/extraction-manifest.json.
|
|
2
|
+
import { execFile } from 'node:child_process';
|
|
3
|
+
import { promisify } from 'node:util';
|
|
4
|
+
import { SemanticProviderError } from './semantic-process.js';
|
|
5
|
+
const semanticEnvironmentAllowlist = [
|
|
6
|
+
'APPDATA',
|
|
7
|
+
'CODEX_HOME',
|
|
8
|
+
'COMSPEC',
|
|
9
|
+
'HOME',
|
|
10
|
+
'HTTP_PROXY',
|
|
11
|
+
'HTTPS_PROXY',
|
|
12
|
+
'LANG',
|
|
13
|
+
'LC_ALL',
|
|
14
|
+
'LOCALAPPDATA',
|
|
15
|
+
'NODE_EXTRA_CA_CERTS',
|
|
16
|
+
'NO_PROXY',
|
|
17
|
+
'PATH',
|
|
18
|
+
'PATHEXT',
|
|
19
|
+
'SSL_CERT_FILE',
|
|
20
|
+
'SYSTEMROOT',
|
|
21
|
+
'TEMP',
|
|
22
|
+
'TMP',
|
|
23
|
+
'USERPROFILE',
|
|
24
|
+
'WINDIR',
|
|
25
|
+
];
|
|
26
|
+
export function semanticCommandEnvironment(source) {
|
|
27
|
+
const result = {};
|
|
28
|
+
for (const allowed of semanticEnvironmentAllowlist) {
|
|
29
|
+
const match = Object.keys(source).find((key) => key.toUpperCase() === allowed);
|
|
30
|
+
if (match && source[match] !== undefined)
|
|
31
|
+
result[allowed] = source[match];
|
|
32
|
+
}
|
|
33
|
+
return result;
|
|
34
|
+
}
|
|
35
|
+
/** Read-only status check. Never expose output: API login status may contain key fragments. */
|
|
36
|
+
export const readCodexLoginMode = async (command, environment, options = {}) => {
|
|
37
|
+
try {
|
|
38
|
+
const result = await promisify(execFile)(command, ['login', 'status'], {
|
|
39
|
+
env: environment, timeout: Math.min(options.timeoutMs ?? 5000, 5000),
|
|
40
|
+
signal: options.signal, maxBuffer: 8192, windowsHide: true,
|
|
41
|
+
});
|
|
42
|
+
const output = `${result.stdout}\n${result.stderr}`;
|
|
43
|
+
if (/logged in using chatgpt/i.test(output))
|
|
44
|
+
return 'chatgpt';
|
|
45
|
+
if (/logged in using an? api key/i.test(output))
|
|
46
|
+
return 'api-key';
|
|
47
|
+
return 'unknown';
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
if (options.signal?.aborted)
|
|
51
|
+
throw new SemanticProviderError('cancelled', 'Semantic extraction cancelled');
|
|
52
|
+
return 'unavailable';
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
/** No auth mutation or fallback. A missing key must not fall back to a saved account. */
|
|
56
|
+
export async function prepareCodexAuthentication(auth, source, command, options = {}, probe = readCodexLoginMode) {
|
|
57
|
+
options.signal?.throwIfAborted();
|
|
58
|
+
const environment = semanticCommandEnvironment(source);
|
|
59
|
+
if (auth?.mode === 'api-key') {
|
|
60
|
+
const keyName = auth.apiKeyEnv ?? 'CODEX_API_KEY';
|
|
61
|
+
const key = source[keyName];
|
|
62
|
+
if (!key?.trim())
|
|
63
|
+
throw new SemanticProviderError('unavailable', `Codex API-key authentication requires the ${keyName} environment variable; no login fallback was attempted`);
|
|
64
|
+
environment.CODEX_API_KEY = key;
|
|
65
|
+
return environment;
|
|
66
|
+
}
|
|
67
|
+
if (auth !== undefined && auth.mode !== 'chatgpt') {
|
|
68
|
+
throw new SemanticProviderError('unavailable', 'Unsupported Codex authentication mode');
|
|
69
|
+
}
|
|
70
|
+
const mode = await probe(command, environment, options);
|
|
71
|
+
if (mode !== 'chatgpt')
|
|
72
|
+
throw new SemanticProviderError('unavailable', 'ChatGPT authentication requires Codex signed in with ChatGPT (codex login). Status could not confirm that login type; no API fallback was attempted');
|
|
73
|
+
return environment;
|
|
74
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { ExtractionContext, GraphFact, SemanticArtifact, SemanticSource } from "@weftlet/core";
|
|
2
|
+
export declare function linkSemanticDocuments(context: ExtractionContext, hydrated: readonly {
|
|
3
|
+
source: SemanticSource;
|
|
4
|
+
artifact: SemanticArtifact;
|
|
5
|
+
}[], trackedPaths?: readonly string[]): readonly GraphFact[];
|