@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.
Files changed (157) hide show
  1. package/LICENSE +176 -0
  2. package/NOTICE +6 -0
  3. package/README.md +155 -2
  4. package/THIRD_PARTY_NOTICES.md +610 -0
  5. package/dist/application/agent-context.d.ts +23 -0
  6. package/dist/application/agent-context.js +94 -0
  7. package/dist/application/application.d.ts +23 -0
  8. package/dist/application/application.js +91 -0
  9. package/dist/application/hydrator.d.ts +49 -0
  10. package/dist/application/hydrator.js +275 -0
  11. package/dist/application/overlay.d.ts +6 -0
  12. package/dist/application/overlay.js +110 -0
  13. package/dist/application/saved-evidence.d.ts +38 -0
  14. package/dist/application/saved-evidence.js +59 -0
  15. package/dist/application/snapshot-checks.d.ts +16 -0
  16. package/dist/application/snapshot-checks.js +51 -0
  17. package/dist/cli.d.ts +3 -0
  18. package/dist/cli.js +4 -0
  19. package/dist/configuration/cache.d.ts +2 -0
  20. package/dist/configuration/cache.js +10 -0
  21. package/dist/configuration/config.d.ts +79 -0
  22. package/dist/configuration/config.js +165 -0
  23. package/dist/core.d.ts +1 -0
  24. package/dist/core.js +2 -0
  25. package/dist/extraction/codex-auth.d.ts +12 -0
  26. package/dist/extraction/codex-auth.js +74 -0
  27. package/dist/extraction/document.d.ts +5 -0
  28. package/dist/extraction/document.js +54 -0
  29. package/dist/extraction/ontology.d.ts +32 -0
  30. package/dist/extraction/ontology.js +164 -0
  31. package/dist/extraction/providers.d.ts +8 -0
  32. package/dist/extraction/providers.js +58 -0
  33. package/dist/extraction/runtime-evidence.d.ts +2 -0
  34. package/dist/extraction/runtime-evidence.js +84 -0
  35. package/dist/extraction/semantic-citations.d.ts +7 -0
  36. package/dist/extraction/semantic-citations.js +27 -0
  37. package/dist/extraction/semantic-process.d.ts +11 -0
  38. package/dist/extraction/semantic-process.js +68 -0
  39. package/dist/extraction/semantic.d.ts +73 -0
  40. package/dist/extraction/semantic.js +414 -0
  41. package/dist/extraction/static.d.ts +52 -0
  42. package/dist/extraction/static.js +322 -0
  43. package/dist/extraction/telemetry.d.ts +19 -0
  44. package/dist/extraction/telemetry.js +259 -0
  45. package/dist/governance/governance.d.ts +60 -0
  46. package/dist/governance/governance.js +209 -0
  47. package/dist/graph/authority.d.ts +2 -0
  48. package/dist/graph/authority.js +13 -0
  49. package/dist/graph/diagnostics.d.ts +5 -0
  50. package/dist/graph/diagnostics.js +61 -0
  51. package/dist/graph/query.d.ts +16 -0
  52. package/dist/graph/query.js +373 -0
  53. package/dist/graph/rdf.d.ts +26 -0
  54. package/dist/graph/rdf.js +144 -0
  55. package/dist/index.d.ts +11 -0
  56. package/dist/index.js +12 -0
  57. package/dist/infrastructure/module-resolution.d.ts +2 -0
  58. package/dist/infrastructure/module-resolution.js +10 -0
  59. package/dist/infrastructure/process.d.ts +5 -0
  60. package/dist/infrastructure/process.js +27 -0
  61. package/dist/infrastructure/util.d.ts +1 -0
  62. package/dist/infrastructure/util.js +2 -0
  63. package/dist/interfaces/cli.d.ts +4 -0
  64. package/dist/interfaces/cli.js +197 -0
  65. package/dist/interfaces/doctor.d.ts +25 -0
  66. package/dist/interfaces/doctor.js +49 -0
  67. package/dist/interfaces/init.d.ts +24 -0
  68. package/dist/interfaces/init.js +154 -0
  69. package/dist/interfaces/mcp-facade.d.ts +3 -0
  70. package/dist/interfaces/mcp-facade.js +4 -0
  71. package/dist/interfaces/mcp.d.ts +2 -0
  72. package/dist/interfaces/mcp.js +81 -0
  73. package/dist/interfaces/request.d.ts +2 -0
  74. package/dist/interfaces/request.js +59 -0
  75. package/dist/mcp.d.ts +3 -0
  76. package/dist/mcp.js +4 -0
  77. package/dist/repository/committed-tree.d.ts +2 -0
  78. package/dist/repository/committed-tree.js +35 -0
  79. package/dist/repository/git.d.ts +26 -0
  80. package/dist/repository/git.js +163 -0
  81. package/dist/repository/glob.d.ts +2 -0
  82. package/dist/repository/glob.js +46 -0
  83. package/dist/repository/working-content.d.ts +3 -0
  84. package/dist/repository/working-content.js +34 -0
  85. package/dist/storage/atomic-rename.d.ts +2 -0
  86. package/dist/storage/atomic-rename.js +22 -0
  87. package/dist/storage/storage.d.ts +15 -0
  88. package/dist/storage/storage.js +115 -0
  89. package/dist/telemetry.d.ts +1 -0
  90. package/dist/telemetry.js +2 -0
  91. package/dist/visualization/viewer-interaction.d.ts +12 -0
  92. package/dist/visualization/viewer-interaction.js +123 -0
  93. package/dist/visualization/visualization.d.ts +26 -0
  94. package/dist/visualization/visualization.js +491 -0
  95. package/docs/ADR-001-standalone-baseline.md +16 -0
  96. package/docs/ADR-002-saved-evidence.md +21 -0
  97. package/docs/ADR-003-draft-overlays.md +23 -0
  98. package/docs/ADR-004-rdf-query-path.md +37 -0
  99. package/docs/ADR-005-governance-and-optional-oml.md +37 -0
  100. package/docs/ADR-006-provider-boundaries.md +15 -0
  101. package/docs/ADR-007-toolkit-modules.md +53 -0
  102. package/docs/ADR-008-weftlet-packages.md +32 -0
  103. package/docs/CLOUD-ENVIRONMENT.md +56 -0
  104. package/docs/ENVIRONMENT-VALIDATION.md +78 -0
  105. package/docs/MIGRATING-TO-WEFTLET.md +54 -0
  106. package/docs/PROVIDERS.md +121 -0
  107. package/docs/RELEASING.md +144 -0
  108. package/docs/ROADMAP.md +53 -0
  109. package/docs/SOURCE-INVENTORY.md +48 -0
  110. package/docs/VALIDATION.md +490 -0
  111. package/docs/VIEWER.md +36 -0
  112. package/docs/architecture-baseline.json +57 -0
  113. package/docs/architecture-policy.md +41 -0
  114. package/docs/extraction-manifest.json +721 -0
  115. package/docs/licenses/REVIEW.md +16 -0
  116. package/docs/licenses/lru-cache-11.5.3-BlueOak.md +55 -0
  117. package/docs/licenses/negotiate-1.0.1-BSD.txt +22 -0
  118. package/docs/licenses/spark-md5-3.0.2-MIT.txt +7 -0
  119. package/examples/generic-typescript/context-graph.config.json +47 -0
  120. package/examples/generic-typescript/context-graph.oml.config.json +49 -0
  121. package/examples/generic-typescript/docs/search-capability.md +14 -0
  122. package/examples/generic-typescript/ontology/build.gradle +38 -0
  123. package/examples/generic-typescript/ontology/catalog.xml +5 -0
  124. package/examples/generic-typescript/ontology/settings.gradle +1 -0
  125. package/examples/generic-typescript/ontology/src/oml/context-graph.dev/core.oml +99 -0
  126. package/examples/generic-typescript/package.json +6 -0
  127. package/examples/generic-typescript/src/search.ts +11 -0
  128. package/examples/generic-typescript/test/search.test.ts +8 -0
  129. package/examples/generic-typescript/tsconfig.json +10 -0
  130. package/examples/pipeline/context-graph.config.json +49 -0
  131. package/examples/pipeline/docs/admission.md +6 -0
  132. package/examples/pipeline/docs/analysis.md +6 -0
  133. package/examples/pipeline/docs/discovery.md +6 -0
  134. package/examples/pipeline/docs/execution.md +6 -0
  135. package/examples/pipeline/docs/outcomes.md +6 -0
  136. package/examples/pipeline/docs/planning.md +6 -0
  137. package/examples/pipeline/package.json +6 -0
  138. package/examples/pipeline/src/admission.ts +9 -0
  139. package/examples/pipeline/src/analysis.ts +9 -0
  140. package/examples/pipeline/src/contracts/admission.ts +1 -0
  141. package/examples/pipeline/src/contracts/analysis.ts +1 -0
  142. package/examples/pipeline/src/contracts/discovery.ts +1 -0
  143. package/examples/pipeline/src/contracts/execution.ts +1 -0
  144. package/examples/pipeline/src/contracts/outcomes.ts +1 -0
  145. package/examples/pipeline/src/contracts/planning.ts +1 -0
  146. package/examples/pipeline/src/discovery.ts +9 -0
  147. package/examples/pipeline/src/execution.ts +9 -0
  148. package/examples/pipeline/src/outcomes.ts +9 -0
  149. package/examples/pipeline/src/planning.ts +9 -0
  150. package/examples/pipeline/src/queue.ts +12 -0
  151. package/examples/pipeline/test/pipeline.test.ts +17 -0
  152. package/examples/pipeline/tsconfig.json +12 -0
  153. package/package.json +116 -3
  154. package/runtime-check.d.mts +2 -0
  155. package/runtime-check.mjs +12 -0
  156. package/templates/context-graph-development/SKILL.md +37 -0
  157. package/third-party-tools.json +34 -0
@@ -0,0 +1,197 @@
1
+ #!/usr/bin/env node
2
+ // Modified for standalone extraction; see docs/extraction-manifest.json.
3
+ import "../../runtime-check.mjs";
4
+ import { resolve } from 'node:path';
5
+ import process from 'node:process';
6
+ import { createContextGraphApplication } from "../application/application.js";
7
+ import { compileOntologyAtExactCheckout } from "../extraction/ontology.js";
8
+ import { visualizeContextPack } from "../visualization/visualization.js";
9
+ import { buildWorkingOverlay } from "../application/overlay.js";
10
+ import { RdfEvidenceGraph } from "../graph/rdf.js";
11
+ import { mkdir, readFile, writeFile } from 'node:fs/promises';
12
+ import { basename, dirname, join } from 'node:path';
13
+ import { analyzeArchitecture } from "../governance/governance.js";
14
+ import { FileSnapshotStore } from "../storage/storage.js";
15
+ import { diagnoseSetup } from "./doctor.js";
16
+ import { runInit } from "./init.js";
17
+ import { loadConfig } from "../configuration/config.js";
18
+ function valueAfter(args, name) {
19
+ const index = args.indexOf(name);
20
+ return index >= 0 ? args[index + 1] : undefined;
21
+ }
22
+ function valuesAfter(args, name) {
23
+ const values = [];
24
+ for (let index = 0; index < args.length; index += 1) {
25
+ if (args[index] === name && args[index + 1])
26
+ values.push(args[index + 1]);
27
+ }
28
+ return values;
29
+ }
30
+ function defaultConfigPath() {
31
+ const local = resolve('context-graph.config.json');
32
+ return local;
33
+ }
34
+ export function impactRequestFromArgs(args) {
35
+ return {
36
+ snapshotId: valueAfter(args, '--snapshot'),
37
+ ownerDocument: valueAfter(args, '--owner-doc'),
38
+ task: valueAfter(args, '--task'),
39
+ checklistItems: valuesAfter(args, '--checklist'),
40
+ changedFiles: valuesAfter(args, '--changed-file'),
41
+ symbols: valuesAfter(args, '--symbol'),
42
+ capabilities: valuesAfter(args, '--capability'),
43
+ maxFacts: valueAfter(args, '--max-facts') ? Number(valueAfter(args, '--max-facts')) : undefined,
44
+ maxDepth: valueAfter(args, '--max-depth') ? Number(valueAfter(args, '--max-depth')) : undefined,
45
+ continuation: valueAfter(args, '--continuation'),
46
+ };
47
+ }
48
+ async function main() {
49
+ const args = process.argv.slice(2);
50
+ if (args.includes('--help') || !args.length) {
51
+ process.stdout.write('weftlet init|config show|config validate|doctor|hydrate|overlay|impact scan|sparql|validate|govern|visualize|diagnostics show|invariant check|clean-compare|ontology compile\nUse --config <file>; queries accept --snapshot <id>. impact scan accepts --changed-file, --capability, --max-facts, --max-depth, --continuation.\ninit [--yes] [--directory <project>] [--config <file>] [--repository-id <id>] [--code-root <path>] [--semantic-source <path>] [--evidence-root <path>] [--model <name>] [--no-gitignore]\nSource flags may be repeated. init never overwrites an existing config. See README.md for examples.\n');
52
+ return;
53
+ }
54
+ if (args[0] === 'init') {
55
+ await runInit(args.slice(1));
56
+ return;
57
+ }
58
+ if (args[0] === 'config') {
59
+ if (!['show', 'validate'].includes(args[1] ?? ''))
60
+ throw new Error('Usage: context-graph config show|validate [--config <file>]');
61
+ const loaded = await loadConfig(valueAfter(args, '--config') ?? defaultConfigPath());
62
+ process.stdout.write(`${JSON.stringify(args[1] === 'show' ? loaded.config :
63
+ { valid: true, configPath: loaded.configPath, repositoryRoot: loaded.repositoryRoot }, null, 2)}\n`);
64
+ return;
65
+ }
66
+ if (args[0] === 'govern') {
67
+ const policyFile = resolve(valueAfter(args, '--policy') ?? 'architecture.policy.json');
68
+ const policy = JSON.parse(await readFile(policyFile, 'utf8'));
69
+ const result = await analyzeArchitecture(dirname(policyFile), policy, basename(policyFile));
70
+ await new FileSnapshotStore(join(dirname(policyFile), '.context-graph')).write(result.snapshot);
71
+ const reportPath = valueAfter(args, '--output');
72
+ if (reportPath) {
73
+ await mkdir(dirname(resolve(reportPath)), { recursive: true });
74
+ await writeFile(resolve(reportPath), `${JSON.stringify(result, null, 2)}\n`);
75
+ process.stdout.write(`${JSON.stringify({ passed: result.passed, findings: result.findings.length, snapshotId: result.snapshot.manifest.snapshotId, reportPath })}\n`);
76
+ }
77
+ else
78
+ process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
79
+ if (!result.passed)
80
+ process.exitCode = 1;
81
+ return;
82
+ }
83
+ const configPath = valueAfter(args, '--config') ?? defaultConfigPath();
84
+ if (args[0] === 'doctor') {
85
+ process.stdout.write(`${JSON.stringify(await diagnoseSetup(configPath), null, 2)}\n`);
86
+ return;
87
+ }
88
+ const application = await createContextGraphApplication(configPath);
89
+ const [command, subcommand] = args;
90
+ if (command === 'sparql' || command === 'validate') {
91
+ const id = valueAfter(args, '--snapshot');
92
+ const snapshot = id ? await application.snapshotStore.read(id) :
93
+ await application.snapshotStore.readHead(application.headName);
94
+ if (!snapshot)
95
+ throw new Error('No graph snapshot is available');
96
+ const graph = new RdfEvidenceGraph(snapshot);
97
+ if (command === 'validate') {
98
+ const report = await graph.validate();
99
+ process.stdout.write(`${JSON.stringify(report, null, 2)}\n`);
100
+ if (!report.conforms)
101
+ process.exitCode = 1;
102
+ }
103
+ else {
104
+ const query = valueAfter(args, '--query-file');
105
+ if (!query)
106
+ throw new Error('sparql requires --query-file <local SELECT query>');
107
+ process.stdout.write(`${JSON.stringify(await graph.select(await readFile(query, 'utf8')), null, 2)}\n`);
108
+ }
109
+ return;
110
+ }
111
+ if (command === 'overlay') {
112
+ const base = valueAfter(args, '--base-snapshot');
113
+ if (!base)
114
+ throw new Error('overlay requires --base-snapshot <committed snapshot ID>');
115
+ const result = await buildWorkingOverlay(application.repository, application.config, application.snapshotStore, application.storageRoot, base, valueAfter(args, '--agent'));
116
+ process.stdout.write(`${JSON.stringify(result.manifest, null, 2)}\n`);
117
+ return;
118
+ }
119
+ if (command === 'hydrate') {
120
+ const controller = new AbortController();
121
+ const cancel = () => controller.abort();
122
+ process.once('SIGINT', cancel);
123
+ const scope = await application.scopeResolver?.resolve(impactRequestFromArgs(args));
124
+ const result = await application.hydrator.hydrate({
125
+ ref: valueAfter(args, '--ref') ?? 'HEAD',
126
+ headName: scope?.headName ?? application.headName,
127
+ semanticSources: scope?.semanticSources,
128
+ forceSemantic: args.includes('--force-semantic'),
129
+ signal: controller.signal,
130
+ onProgress: args.includes('--quiet') ? undefined : (event) => process.stderr.write(`[context-graph] ${event.phase}${event.total === undefined ? '' : ` ${event.completed}/${event.total}`}\n`),
131
+ });
132
+ process.removeListener('SIGINT', cancel);
133
+ process.stdout.write(`${JSON.stringify({
134
+ snapshotId: result.snapshot.manifest.snapshotId,
135
+ commitSha: result.snapshot.manifest.commitSha,
136
+ contentDigest: result.snapshot.manifest.contentDigest,
137
+ semanticExtracted: result.semanticExtracted,
138
+ semanticReused: result.semanticReused,
139
+ }, null, 2)}\n`);
140
+ return;
141
+ }
142
+ if (command === 'clean-compare') {
143
+ const result = await application.snapshotChecks.compare(impactRequestFromArgs(args), valueAfter(args, '--ref') ?? 'HEAD');
144
+ process.stdout.write(`${result.snapshot.manifest.contentDigest}\n`);
145
+ return;
146
+ }
147
+ if (command === 'impact' && subcommand === 'scan') {
148
+ const pack = await application.queryService.scan(impactRequestFromArgs(args));
149
+ process.stdout.write(args.includes('--json') ? `${JSON.stringify(pack, null, 2)}\n` : `${pack.markdown}\n`);
150
+ return;
151
+ }
152
+ if (command === 'visualize') {
153
+ const outputPath = valueAfter(args, '--output');
154
+ if (!outputPath)
155
+ throw new Error('visualize requires --output <path>');
156
+ const result = await visualizeContextPack(application.queryService, impactRequestFromArgs(args), {
157
+ outputPath,
158
+ format: valueAfter(args, '--format'),
159
+ force: args.includes('--force'),
160
+ sourceUrlTemplate: valueAfter(args, '--source-url-template'),
161
+ });
162
+ process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
163
+ return;
164
+ }
165
+ if (command === 'invariant' && subcommand === 'check') {
166
+ const snapshot = await application.snapshotChecks.read(impactRequestFromArgs(args), valueAfter(args, '--ref') ?? 'HEAD');
167
+ process.stdout.write(`${JSON.stringify(snapshot.invariants, null, 2)}\n`);
168
+ if (snapshot.invariants.some((item) => item.status !== 'pass'))
169
+ process.exitCode = 1;
170
+ return;
171
+ }
172
+ if (command === 'diagnostics' && subcommand === 'show') {
173
+ const snapshotId = valueAfter(args, '--snapshot');
174
+ if (!snapshotId)
175
+ throw new Error('diagnostics show requires --snapshot <snapshotId> from a Context Pack');
176
+ const snapshot = await application.snapshotStore.read(snapshotId);
177
+ if (!snapshot)
178
+ throw new Error('No graph snapshot is available for that identifier');
179
+ process.stdout.write(`${JSON.stringify({
180
+ snapshotId,
181
+ diagnostics: snapshot.diagnostics ?? [],
182
+ legacyUnclassified: snapshot.unresolved,
183
+ invariants: snapshot.invariants,
184
+ }, null, 2)}\n`);
185
+ return;
186
+ }
187
+ if (command === 'ontology' && subcommand === 'compile') {
188
+ const result = await compileOntologyAtExactCheckout(application.repository, application.ontologyCompiler, valueAfter(args, '--ref') ?? 'HEAD');
189
+ process.stdout.write(`${JSON.stringify({ digest: result.digest, files: result.rdfFiles }, null, 2)}\n`);
190
+ return;
191
+ }
192
+ throw new Error('Usage: weftlet hydrate|clean-compare|impact scan|diagnostics show --snapshot <id>|invariant check|ontology compile|visualize --output <file.html|file.dot|file.svg>');
193
+ }
194
+ main().catch((error) => {
195
+ process.stderr.write(`${error instanceof Error ? (error.stack ?? error.message) : String(error)}\n`);
196
+ process.exitCode = 1;
197
+ });
@@ -0,0 +1,25 @@
1
+ /** Reads readiness only. It never installs tools, prints credentials, or starts a model. */
2
+ export declare function diagnoseSetup(configPath: string): Promise<{
3
+ node: string;
4
+ repositoryRoot: string;
5
+ gitAvailable: boolean;
6
+ savedSnapshot: string | null;
7
+ semanticProvider: "codex-cli" | "module";
8
+ codexAvailable: boolean;
9
+ codexAuthMode: "chatgpt" | "api-key" | undefined;
10
+ codexLoginMode: import("../extraction/codex-auth.js").CodexLoginMode | undefined;
11
+ codexApiKeyEnv: string | undefined;
12
+ codexApiKeyPresent: boolean | undefined;
13
+ codexAuthenticated: boolean | null;
14
+ codexAuthStatus: string;
15
+ moduleProvider: string | undefined;
16
+ ontologyBackend: "rdf" | "opencaesar";
17
+ dockerReady: boolean | undefined;
18
+ limits: {
19
+ maxDocuments: number;
20
+ maxSourceBytes: number;
21
+ timeoutMs: number;
22
+ retries: number;
23
+ };
24
+ note: string;
25
+ }>;
@@ -0,0 +1,49 @@
1
+ import { execFile } from 'node:child_process';
2
+ import { promisify } from 'node:util';
3
+ import { loadConfig } from "../configuration/config.js";
4
+ import { resolveStorageRoot } from "../configuration/cache.js";
5
+ import { FileSnapshotStore } from "../storage/storage.js";
6
+ import { readCodexLoginMode, semanticCommandEnvironment } from '../extraction/codex-auth.js';
7
+ /** Reads readiness only. It never installs tools, prints credentials, or starts a model. */
8
+ export async function diagnoseSetup(configPath) {
9
+ const { config, repositoryRoot } = await loadConfig(configPath);
10
+ const execute = promisify(execFile);
11
+ const probe = async (name, args) => {
12
+ try {
13
+ const result = await execute(name, args, { cwd: repositoryRoot, timeout: 5000, maxBuffer: 8192 });
14
+ return { available: true, output: `${result.stdout}${result.stderr}` };
15
+ }
16
+ catch {
17
+ return { available: false, output: '' };
18
+ }
19
+ };
20
+ const git = await probe('git', ['--version']);
21
+ const codex = await probe(process.platform === 'win32' ? 'codex.exe' : 'codex', ['--version']);
22
+ const auth = config.semantic.auth;
23
+ const authMode = config.semantic.provider === 'codex-cli' ? auth?.mode ?? 'chatgpt' : undefined;
24
+ const apiKeyEnv = auth?.mode === 'api-key' ? auth.apiKeyEnv ?? 'CODEX_API_KEY' : undefined;
25
+ const apiKeyPresent = apiKeyEnv ? Boolean(process.env[apiKeyEnv]?.trim()) : undefined;
26
+ const loginMode = codex.available && authMode === 'chatgpt'
27
+ ? await readCodexLoginMode(process.platform === 'win32' ? 'codex.exe' : 'codex', semanticCommandEnvironment(process.env))
28
+ : undefined;
29
+ const docker = config.ontology.backend === 'opencaesar' ? await probe('docker', ['info']) : undefined;
30
+ const saved = await new FileSnapshotStore(resolveStorageRoot(repositoryRoot, config)).readHead('default');
31
+ return {
32
+ node: process.version, repositoryRoot, gitAvailable: git.available,
33
+ savedSnapshot: saved?.manifest.snapshotId ?? null,
34
+ semanticProvider: config.semantic.provider,
35
+ codexAvailable: codex.available,
36
+ codexAuthMode: authMode,
37
+ codexLoginMode: loginMode,
38
+ codexApiKeyEnv: apiKeyEnv,
39
+ codexApiKeyPresent: apiKeyPresent,
40
+ codexAuthenticated: authMode === 'chatgpt' ? loginMode === 'chatgpt' : null,
41
+ codexAuthStatus: authMode === 'api-key' ? (apiKeyPresent ? 'key-present-unverified' : 'missing-key')
42
+ : authMode === 'chatgpt' ? (loginMode === 'chatgpt' ? 'chatgpt-login-present' : 'chatgpt-login-required') : 'not-applicable',
43
+ moduleProvider: config.semantic.provider === 'module' ? 'loaded only during extraction; trusted local code' : undefined,
44
+ ontologyBackend: config.ontology.backend ?? 'rdf', dockerReady: docker?.available,
45
+ limits: { maxDocuments: config.semantic.maxDocuments ?? 8, maxSourceBytes: config.semantic.maxSourceBytes ?? 65_536,
46
+ timeoutMs: config.semantic.timeoutMs ?? 120_000, retries: config.semantic.retries ?? 0 },
47
+ note: 'Saved evidence remains queryable without model or Docker readiness. No model request was made.',
48
+ };
49
+ }
@@ -0,0 +1,24 @@
1
+ import type { ContextGraphConfig } from "../configuration/config.js";
2
+ export interface InitOptions {
3
+ directory?: string;
4
+ configPath?: string;
5
+ repositoryId?: string;
6
+ codeRoots?: string[];
7
+ semanticSources?: string[];
8
+ evidenceRoots?: string[];
9
+ model?: string;
10
+ gitignore?: boolean;
11
+ }
12
+ /** Creates local configuration only; no provider, indexer, Git mutation or install is invoked. */
13
+ export declare function prepareProjectConfig(options?: InitOptions): Promise<{
14
+ root: string;
15
+ configPath: string;
16
+ config: ContextGraphConfig;
17
+ }>;
18
+ export declare function initializeProject(options?: InitOptions): Promise<{
19
+ root: string;
20
+ configPath: string;
21
+ config: ContextGraphConfig;
22
+ }>;
23
+ /** TTY prompts are optional; automation uses --yes and explicit source flags. */
24
+ export declare function runInit(args: readonly string[]): Promise<void>;
@@ -0,0 +1,154 @@
1
+ import { lstat, mkdir, readFile, appendFile, writeFile } from 'node:fs/promises';
2
+ import { basename, dirname, isAbsolute, relative, resolve } from 'node:path';
3
+ import { createInterface } from 'node:readline/promises';
4
+ const toPosix = (path) => path.replace(/\\/g, '/');
5
+ async function present(path) {
6
+ try {
7
+ await lstat(path);
8
+ return true;
9
+ }
10
+ catch (error) {
11
+ if (error.code === 'ENOENT')
12
+ return false;
13
+ throw error;
14
+ }
15
+ }
16
+ /** Creates local configuration only; no provider, indexer, Git mutation or install is invoked. */
17
+ export async function prepareProjectConfig(options = {}) {
18
+ const root = resolve(options.directory ?? '.');
19
+ const configPath = resolve(root, options.configPath ?? 'context-graph.config.json');
20
+ let packageName;
21
+ if (await present(resolve(root, 'package.json'))) {
22
+ const manifest = JSON.parse(await readFile(resolve(root, 'package.json'), 'utf8'));
23
+ if (typeof manifest.name === 'string' && manifest.name.trim())
24
+ packageName = manifest.name;
25
+ }
26
+ const repositoryId = options.repositoryId ?? packageName ?? basename(root);
27
+ if (!repositoryId.trim())
28
+ throw new Error('Repository ID must not be empty');
29
+ const paths = (values) => values.map((path) => {
30
+ const normalized = toPosix(path).replace(/^\.\//, '');
31
+ if (!normalized || normalized.startsWith('/') || isAbsolute(path) || /^[A-Za-z]:/.test(normalized) ||
32
+ normalized.split('/').includes('..'))
33
+ throw new Error(`Source paths must stay within the repository: ${path}`);
34
+ return normalized;
35
+ });
36
+ const detectedEvidence = [];
37
+ for (const candidate of ['test', 'tests', '__tests__']) {
38
+ if (await present(resolve(root, candidate)))
39
+ detectedEvidence.push(candidate);
40
+ }
41
+ const config = {
42
+ schemaVersion: 1,
43
+ repositoryRoot: toPosix(relative(dirname(configPath), root)) || '.',
44
+ repositoryId,
45
+ namespace: `https://example.test/context/${encodeURIComponent(repositoryId)}/`,
46
+ semanticSources: paths(options.semanticSources ?? []),
47
+ approvedIntentSources: [],
48
+ codeRoots: paths(options.codeRoots ?? [await present(resolve(root, 'src')) ? 'src' : '.']),
49
+ contractPatterns: [],
50
+ evidenceRoots: paths(options.evidenceRoots ?? detectedEvidence),
51
+ semantic: {
52
+ provider: 'codex-cli', auth: { mode: 'chatgpt' }, model: options.model ?? 'gpt-6-astra', reasoningEffort: 'medium',
53
+ promptVersion: 'generic-semantic-v3', extractorVersion: '1.1.0', adapterVersion: '1.1.0',
54
+ ontologyVersion: '1.1.0', minimumConfidence: 0.7, failOnUnresolved: true, concurrency: 1,
55
+ maxDocuments: 8, maxSourceBytes: 65_536, timeoutMs: 120_000, retries: 0,
56
+ },
57
+ scip: { version: '0.4.0', command: 'bundled-scip-typescript',
58
+ args: ['index', '--no-global-caches', '--infer-tsconfig'] },
59
+ ontology: { backend: 'rdf', toolVersion: 'rdf-record-v1' },
60
+ runtimeEvidence: [], storagePath: '.context-graph',
61
+ };
62
+ if (!config.semantic.model.trim())
63
+ throw new Error('Model must not be empty');
64
+ return { root, configPath, config };
65
+ }
66
+ export async function initializeProject(options = {}) {
67
+ const prepared = await prepareProjectConfig(options);
68
+ const { root, configPath, config } = prepared;
69
+ if (await present(configPath))
70
+ throw new Error(`Configuration already exists: ${configPath}. Edit it or choose another --config path.`);
71
+ const ignorePath = resolve(root, '.gitignore');
72
+ if (configPath === ignorePath)
73
+ throw new Error('Configuration cannot replace .gitignore');
74
+ let ignoreContent = '';
75
+ if (options.gitignore !== false && await present(ignorePath)) {
76
+ if (!(await lstat(ignorePath)).isFile())
77
+ throw new Error('.gitignore must be a regular file');
78
+ ignoreContent = await readFile(ignorePath, 'utf8');
79
+ }
80
+ await mkdir(dirname(configPath), { recursive: true });
81
+ // Exclusive creation prevents a second initializer from overwriting a concurrent writer.
82
+ await writeFile(configPath, `${JSON.stringify(config, null, 2)}\n`, { flag: 'wx' });
83
+ const ignoresStorage = ignoreContent.split(/\r?\n/).some((line) => ['.context-graph', '.context-graph/', '/.context-graph', '/.context-graph/'].includes(line.trim()));
84
+ if (options.gitignore !== false && !ignoresStorage) {
85
+ const newline = ignoreContent.includes('\r\n') ? '\r\n' : '\n';
86
+ await appendFile(ignorePath, `${ignoreContent && !ignoreContent.endsWith('\n') ? newline : ''}.context-graph/${newline}`);
87
+ }
88
+ return prepared;
89
+ }
90
+ /** TTY prompts are optional; automation uses --yes and explicit source flags. */
91
+ export async function runInit(args) {
92
+ const values = new Map();
93
+ const flags = new Set(['--yes', '--no-gitignore']);
94
+ const valued = new Set(['--directory', '--config', '--repository-id', '--code-root', '--semantic-source', '--evidence-root', '--model']);
95
+ for (let i = 0; i < args.length; i++) {
96
+ const key = args[i];
97
+ if (flags.has(key))
98
+ continue;
99
+ if (!valued.has(key))
100
+ throw new Error(`Unknown init option: ${key}`);
101
+ const value = args[++i];
102
+ if (!value || value.startsWith('--'))
103
+ throw new Error(`${key} requires a value`);
104
+ values.set(key, [...(values.get(key) ?? []), value]);
105
+ }
106
+ const one = (key) => {
107
+ const entries = values.get(key);
108
+ if (entries && entries.length > 1)
109
+ throw new Error(`${key} may only be supplied once`);
110
+ return entries?.[0];
111
+ };
112
+ const options = {
113
+ directory: one('--directory'), configPath: one('--config'), repositoryId: one('--repository-id'),
114
+ model: one('--model'), codeRoots: values.get('--code-root'), semanticSources: values.get('--semantic-source'),
115
+ evidenceRoots: values.get('--evidence-root'), gitignore: !args.includes('--no-gitignore'),
116
+ };
117
+ if (!args.includes('--yes')) {
118
+ if (!process.stdin.isTTY || !process.stdout.isTTY)
119
+ throw new Error('Non-interactive setup requires --yes. Use --semantic-source <path> to select documents explicitly.');
120
+ const defaults = await prepareProjectConfig(options);
121
+ const prompt = createInterface({ input: process.stdin, output: process.stdout });
122
+ try {
123
+ process.stdout.write('Set up a TypeScript context graph. Documents are opt-in; source approval is configured separately.\n');
124
+ if (!options.repositoryId)
125
+ options.repositoryId = (await prompt.question(`Repository ID [${defaults.config.repositoryId}]: `)).trim() || defaults.config.repositoryId;
126
+ if (!options.codeRoots) {
127
+ const answer = (await prompt.question(`Code roots, comma-separated [${defaults.config.codeRoots.join(', ')}]: `)).trim();
128
+ if (answer)
129
+ options.codeRoots = answer.split(',').map((path) => path.trim()).filter(Boolean);
130
+ }
131
+ if (!options.semanticSources) {
132
+ const answer = (await prompt.question('Document paths, comma-separated [none]: ')).trim();
133
+ options.semanticSources = answer ? answer.split(',').map((path) => path.trim()).filter(Boolean) : [];
134
+ }
135
+ if (!options.model)
136
+ options.model = (await prompt.question(`Codex model [${defaults.config.semantic.model}]: `)).trim() || defaults.config.semantic.model;
137
+ const confirm = (await prompt.question(`Write ${defaults.configPath}${options.gitignore ? ' and update .gitignore' : ''}? [Y/n]: `)).trim();
138
+ if (confirm && !/^y(?:es)?$/i.test(confirm)) {
139
+ process.stdout.write('Setup cancelled; no files written.\n');
140
+ return;
141
+ }
142
+ }
143
+ finally {
144
+ prompt.close();
145
+ }
146
+ }
147
+ const result = await initializeProject(options);
148
+ process.stdout.write(`Created ${result.configPath}\n`);
149
+ process.stdout.write(`Selected ${result.config.semanticSources.length} document paths; approved intent sources: none.\n`);
150
+ process.stdout.write('Review the configuration, select any contracts and owner-approved documents, and commit the inputs you want indexed.\n');
151
+ const configArg = JSON.stringify(result.configPath);
152
+ process.stdout.write(`Next: npx @weftlet/toolkit doctor --config ${configArg}\nThen: npx @weftlet/toolkit hydrate --config ${configArg} --ref HEAD\n`);
153
+ process.stdout.write('Hydration with selected documents uses Codex and may incur model charges. Setup made no model requests.\n');
154
+ }
@@ -0,0 +1,3 @@
1
+ import type { ImpactQueryService, ImpactScanRequest } from "@weftlet/core";
2
+ export declare const impactScanToolName = "impact.scan";
3
+ export declare function handleImpactScan(service: ImpactQueryService, request: ImpactScanRequest): Promise<import("@weftlet/core").ContextPack>;
@@ -0,0 +1,4 @@
1
+ export const impactScanToolName = 'impact.scan';
2
+ export async function handleImpactScan(service, request) {
3
+ return service.scan(request);
4
+ }
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ import "../../runtime-check.mjs";
@@ -0,0 +1,81 @@
1
+ #!/usr/bin/env node
2
+ // Modified for standalone extraction; see docs/extraction-manifest.json.
3
+ import "../../runtime-check.mjs";
4
+ import { resolve } from 'node:path';
5
+ import process from 'node:process';
6
+ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
7
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
8
+ import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js';
9
+ import { createContextGraphApplication } from "../application/application.js";
10
+ import { handleImpactScan, impactScanToolName } from "./mcp-facade.js";
11
+ import { parseImpactScanRequest } from "./request.js";
12
+ async function main() {
13
+ const local = resolve('context-graph.config.json');
14
+ const configPath = process.env.CONTEXT_GRAPH_CONFIG ??
15
+ local;
16
+ const application = await createContextGraphApplication(configPath);
17
+ const server = new Server({ name: 'context-graph', version: '0.1.0' }, {
18
+ capabilities: { tools: {} },
19
+ instructions: 'Call impact.scan before non-leaf planning or implementation. Pass the active owner document and task/checklist text. Treat product-intent, contract, implementation, and validation coverage gaps as unresolved context, not permission to invent behavior.',
20
+ });
21
+ server.setRequestHandler(ListToolsRequestSchema, async () => ({
22
+ tools: [
23
+ {
24
+ name: impactScanToolName,
25
+ description: 'Return a bounded, provenance-bearing impact Context Pack for a task, document, file, symbol, or capability.',
26
+ inputSchema: {
27
+ type: 'object',
28
+ additionalProperties: false,
29
+ properties: {
30
+ snapshotId: { type: 'string' },
31
+ ownerDocument: { type: 'string' },
32
+ task: { type: 'string' },
33
+ checklistItems: { type: 'array', items: { type: 'string' } },
34
+ changedFiles: { type: 'array', items: { type: 'string' } },
35
+ symbols: { type: 'array', items: { type: 'string' } },
36
+ capabilities: { type: 'array', items: { type: 'string' } },
37
+ maxFacts: { type: 'integer', minimum: 1, maximum: 200 },
38
+ maxDepth: { type: 'integer', minimum: 1, maximum: 128 },
39
+ continuation: { type: 'string', maxLength: 2048 },
40
+ },
41
+ },
42
+ annotations: {
43
+ readOnlyHint: true,
44
+ destructiveHint: false,
45
+ idempotentHint: true,
46
+ openWorldHint: false,
47
+ },
48
+ },
49
+ ],
50
+ }));
51
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
52
+ if (request.params.name !== impactScanToolName) {
53
+ return {
54
+ content: [{ type: 'text', text: `Unknown tool: ${request.params.name}` }],
55
+ isError: true,
56
+ };
57
+ }
58
+ try {
59
+ const pack = await handleImpactScan(application.queryService, parseImpactScanRequest(request.params.arguments ?? {}));
60
+ return {
61
+ content: [
62
+ { type: 'text', text: pack.markdown },
63
+ { type: 'text', text: JSON.stringify(pack) },
64
+ ],
65
+ };
66
+ }
67
+ catch (error) {
68
+ return {
69
+ content: [{ type: 'text', text: error instanceof Error ? error.message : String(error) }],
70
+ isError: true,
71
+ };
72
+ }
73
+ });
74
+ await server.connect(new StdioServerTransport());
75
+ }
76
+ if (process.env.CONTEXT_GRAPH_MCP_TEST !== '1') {
77
+ void main().catch((error) => {
78
+ process.stderr.write(`${error instanceof Error ? (error.stack ?? error.message) : String(error)}\n`);
79
+ process.exitCode = 1;
80
+ });
81
+ }
@@ -0,0 +1,2 @@
1
+ import type { ImpactScanRequest } from "@weftlet/core";
2
+ export declare function parseImpactScanRequest(value: unknown): ImpactScanRequest;
@@ -0,0 +1,59 @@
1
+ const allowedFields = new Set([
2
+ 'snapshotId',
3
+ 'ownerDocument',
4
+ 'task',
5
+ 'checklistItems',
6
+ 'changedFiles',
7
+ 'symbols',
8
+ 'capabilities',
9
+ 'maxFacts',
10
+ 'maxDepth',
11
+ 'continuation',
12
+ ]);
13
+ function optionalString(value, field) {
14
+ if (value === undefined)
15
+ return undefined;
16
+ if (typeof value !== 'string' || value.trim().length === 0) {
17
+ throw new Error(`${field} must be a non-empty string`);
18
+ }
19
+ return value;
20
+ }
21
+ function optionalStringArray(value, field) {
22
+ if (value === undefined)
23
+ return undefined;
24
+ if (!Array.isArray(value) ||
25
+ !value.every((item) => typeof item === 'string' && item.trim().length > 0)) {
26
+ throw new Error(`${field} must be an array of non-empty strings`);
27
+ }
28
+ return value;
29
+ }
30
+ export function parseImpactScanRequest(value) {
31
+ if (typeof value !== 'object' || value === null || Array.isArray(value)) {
32
+ throw new Error('impact.scan arguments must be an object');
33
+ }
34
+ const record = value;
35
+ const unexpected = Object.keys(record).filter((field) => !allowedFields.has(field));
36
+ if (unexpected.length > 0)
37
+ throw new Error(`Unknown impact.scan fields: ${unexpected.join(', ')}`);
38
+ const maxFacts = record.maxFacts;
39
+ const maxDepth = record.maxDepth;
40
+ if (maxDepth !== undefined && (!Number.isInteger(maxDepth) || Number(maxDepth) < 1 || Number(maxDepth) > 128)) {
41
+ throw new Error('maxDepth must be an integer between 1 and 128');
42
+ }
43
+ if (maxFacts !== undefined &&
44
+ (!Number.isInteger(maxFacts) || Number(maxFacts) < 1 || Number(maxFacts) > 200)) {
45
+ throw new Error('maxFacts must be an integer between 1 and 200');
46
+ }
47
+ return {
48
+ snapshotId: optionalString(record.snapshotId, 'snapshotId'),
49
+ ownerDocument: optionalString(record.ownerDocument, 'ownerDocument'),
50
+ task: optionalString(record.task, 'task'),
51
+ checklistItems: optionalStringArray(record.checklistItems, 'checklistItems'),
52
+ changedFiles: optionalStringArray(record.changedFiles, 'changedFiles'),
53
+ symbols: optionalStringArray(record.symbols, 'symbols'),
54
+ capabilities: optionalStringArray(record.capabilities, 'capabilities'),
55
+ maxFacts: maxFacts,
56
+ maxDepth: maxDepth,
57
+ continuation: optionalString(record.continuation, 'continuation'),
58
+ };
59
+ }
package/dist/mcp.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ import '../runtime-check.mjs';
3
+ import './interfaces/mcp.js';
package/dist/mcp.js ADDED
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env node
2
+ // Stable executable entry point; implementation lives in interfaces/.
3
+ import '../runtime-check.mjs';
4
+ import './interfaces/mcp.js';
@@ -0,0 +1,2 @@
1
+ /** Materialize committed bytes without registering a worktree or touching the active checkout. */
2
+ export declare function withCommittedTree<T>(repositoryRoot: string, commitSha: string, operation: (root: string) => Promise<T>): Promise<T>;