codehere 1.0.1 → 1.0.3

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 (163) hide show
  1. package/README.md +35 -193
  2. package/dist/application/use-cases/check-claimed-tests.js +81 -0
  3. package/dist/application/use-cases/detect-test-weakening.js +237 -0
  4. package/dist/application/use-cases/rerun-tests.js +110 -7
  5. package/dist/index.js +38 -1212
  6. package/dist/infrastructure/orchestration/artifacts-extractor.js +2 -323
  7. package/dist/presentation/cli/commands/check-command.js +178 -23
  8. package/dist/presentation/cli/commands/help-command.js +21 -285
  9. package/dist/ui.js +5 -236
  10. package/dist/utils/version.js +0 -6
  11. package/package.json +9 -38
  12. package/dist/application/services/verdict-service.js +0 -154
  13. package/dist/application/use-cases/assess-cross-vendor.js +0 -126
  14. package/dist/application/use-cases/build-cross-vendor-verdict.js +0 -118
  15. package/dist/application/use-cases/domain-trust.js +0 -110
  16. package/dist/application/use-cases/export-aat.js +0 -107
  17. package/dist/application/use-cases/generate-acceptance-criteria.js +0 -223
  18. package/dist/application/use-cases/generate-final-report.js +0 -259
  19. package/dist/application/use-cases/generate-narration.js +0 -130
  20. package/dist/application/use-cases/generate-thread-title.js +0 -181
  21. package/dist/application/use-cases/narrator-prompt-template.js +0 -158
  22. package/dist/application/use-cases/plan-auto-review.js +0 -57
  23. package/dist/application/use-cases/reconcile-audit.js +0 -100
  24. package/dist/application/use-cases/search-sessions.js +0 -70
  25. package/dist/application/use-cases/should-auto-review.js +0 -71
  26. package/dist/application/use-cases/thread-export.js +0 -245
  27. package/dist/application/use-cases/verify-claim.js +0 -456
  28. package/dist/application/use-cases/write-pass.js +0 -277
  29. package/dist/domain/audit.js +0 -11
  30. package/dist/domain/services/codebase-detector.js +0 -85
  31. package/dist/domain/task.js +0 -91
  32. package/dist/error-handler.js +0 -139
  33. package/dist/infrastructure/completion/bash-completion.js +0 -29
  34. package/dist/infrastructure/completion/fish-completion.js +0 -17
  35. package/dist/infrastructure/completion/zsh-completion.js +0 -25
  36. package/dist/infrastructure/config/config-manager.js +0 -174
  37. package/dist/infrastructure/config/product-context.js +0 -49
  38. package/dist/infrastructure/context/adapters/drawio-source.js +0 -157
  39. package/dist/infrastructure/context/adapters/figma-source.js +0 -198
  40. package/dist/infrastructure/context/adapters/github-issue-source.js +0 -160
  41. package/dist/infrastructure/context/adapters/linear-source.js +0 -190
  42. package/dist/infrastructure/context/context-resolver.js +0 -144
  43. package/dist/infrastructure/context/context-source.js +0 -114
  44. package/dist/infrastructure/context/github-api.js +0 -110
  45. package/dist/infrastructure/context/github-device-flow.js +0 -65
  46. package/dist/infrastructure/context/oauth-flow.js +0 -207
  47. package/dist/infrastructure/context/oauth-store.js +0 -122
  48. package/dist/infrastructure/context/registry-bootstrap.js +0 -95
  49. package/dist/infrastructure/context/write-pass-resolver.js +0 -106
  50. package/dist/infrastructure/cost/budget-config.js +0 -114
  51. package/dist/infrastructure/cost/cost-router.js +0 -97
  52. package/dist/infrastructure/cost/spend-aggregator.js +0 -222
  53. package/dist/infrastructure/cost/team-ledger.js +0 -355
  54. package/dist/infrastructure/discovery/discovery-log.js +0 -85
  55. package/dist/infrastructure/hooks/audit-tip-signature.js +0 -336
  56. package/dist/infrastructure/hooks/claude-sdk-hook.js +0 -1018
  57. package/dist/infrastructure/hooks/hook-mode.js +0 -87
  58. package/dist/infrastructure/hooks/mirror-tool-call-to-audit.js +0 -49
  59. package/dist/infrastructure/hooks/pre-prompt-scan.js +0 -122
  60. package/dist/infrastructure/hooks/session-analyzer.js +0 -177
  61. package/dist/infrastructure/observability/execution-tracer.js +0 -227
  62. package/dist/infrastructure/orchestration/agent-adapter.js +0 -13
  63. package/dist/infrastructure/orchestration/agent-installer.js +0 -101
  64. package/dist/infrastructure/orchestration/agents-config-seed.js +0 -68
  65. package/dist/infrastructure/orchestration/aider-adapter.js +0 -611
  66. package/dist/infrastructure/orchestration/cassette-recorder.js +0 -120
  67. package/dist/infrastructure/orchestration/claude-code-adapter.js +0 -592
  68. package/dist/infrastructure/orchestration/codex-adapter.js +0 -574
  69. package/dist/infrastructure/orchestration/conductor-adapter.js +0 -416
  70. package/dist/infrastructure/orchestration/context-handoff.js +0 -162
  71. package/dist/infrastructure/orchestration/control/codex-control-surface.js +0 -99
  72. package/dist/infrastructure/orchestration/control/control-surface.js +0 -15
  73. package/dist/infrastructure/orchestration/detect-ollama.js +0 -75
  74. package/dist/infrastructure/orchestration/failover-classifier.js +0 -151
  75. package/dist/infrastructure/orchestration/failover-config.js +0 -67
  76. package/dist/infrastructure/orchestration/failover-policy.js +0 -106
  77. package/dist/infrastructure/orchestration/gemini-adapter.js +0 -663
  78. package/dist/infrastructure/orchestration/generic-cli-adapter.js +0 -689
  79. package/dist/infrastructure/orchestration/graphify-context.js +0 -209
  80. package/dist/infrastructure/orchestration/memory-injector.js +0 -380
  81. package/dist/infrastructure/orchestration/mocks/mock-agent-repl.js +0 -388
  82. package/dist/infrastructure/orchestration/mocks/mock-codex-app-server.js +0 -73
  83. package/dist/infrastructure/orchestration/mocks/mock-synthesis.js +0 -83
  84. package/dist/infrastructure/orchestration/mocks/resolve-agent-spawn.js +0 -37
  85. package/dist/infrastructure/orchestration/opencode-adapter.js +0 -580
  86. package/dist/infrastructure/orchestration/progress-aggregator.js +0 -906
  87. package/dist/infrastructure/orchestration/readiness.js +0 -103
  88. package/dist/infrastructure/orchestration/session-manager.js +0 -1590
  89. package/dist/infrastructure/orchestration/session-persistence.js +0 -394
  90. package/dist/infrastructure/orchestration/thread-key-store.js +0 -127
  91. package/dist/infrastructure/orchestration/write-pass-store.js +0 -38
  92. package/dist/infrastructure/protocols/cloud-sync.js +0 -207
  93. package/dist/infrastructure/protocols/mcp-helpers.js +0 -25
  94. package/dist/infrastructure/protocols/mcp-server-impl.js +0 -3935
  95. package/dist/infrastructure/protocols/mcp-tools.js +0 -401
  96. package/dist/infrastructure/protocols/probe-port.js +0 -36
  97. package/dist/infrastructure/protocols/thread-phase.js +0 -105
  98. package/dist/infrastructure/protocols/ui/app-script-artifacts.js +0 -121
  99. package/dist/infrastructure/protocols/ui/app-script-audit-footer.js +0 -44
  100. package/dist/infrastructure/protocols/ui/app-script-audit-verify.js +0 -56
  101. package/dist/infrastructure/protocols/ui/app-script-composer-cost.js +0 -588
  102. package/dist/infrastructure/protocols/ui/app-script-context-sources.js +0 -168
  103. package/dist/infrastructure/protocols/ui/app-script-conversation.js +0 -67
  104. package/dist/infrastructure/protocols/ui/app-script-drawer.js +0 -115
  105. package/dist/infrastructure/protocols/ui/app-script-failover.js +0 -133
  106. package/dist/infrastructure/protocols/ui/app-script-github.js +0 -113
  107. package/dist/infrastructure/protocols/ui/app-script-observer.js +0 -104
  108. package/dist/infrastructure/protocols/ui/app-script-onboarding.js +0 -80
  109. package/dist/infrastructure/protocols/ui/app-script-overlay.js +0 -203
  110. package/dist/infrastructure/protocols/ui/app-script-recovery.js +0 -136
  111. package/dist/infrastructure/protocols/ui/app-script-render.js +0 -166
  112. package/dist/infrastructure/protocols/ui/app-script-share.js +0 -78
  113. package/dist/infrastructure/protocols/ui/app-script-sidebar.js +0 -194
  114. package/dist/infrastructure/protocols/ui/app-script-tour.js +0 -78
  115. package/dist/infrastructure/protocols/ui/app-script-wpass.js +0 -312
  116. package/dist/infrastructure/protocols/ui/app-script.js +0 -2998
  117. package/dist/infrastructure/protocols/ui/font-faces.js +0 -7
  118. package/dist/infrastructure/protocols/ui/styles.js +0 -2147
  119. package/dist/infrastructure/protocols/ui/tokens.js +0 -96
  120. package/dist/infrastructure/protocols/ui-page-v2.js +0 -320
  121. package/dist/infrastructure/protocols/verify-page.js +0 -1677
  122. package/dist/infrastructure/security/file-collection.js +0 -118
  123. package/dist/infrastructure/security/fix-code.js +0 -144
  124. package/dist/infrastructure/security/fix-secret.js +0 -89
  125. package/dist/infrastructure/security/permissions-config.js +0 -235
  126. package/dist/infrastructure/security/scan-types.js +0 -10
  127. package/dist/infrastructure/security/ship-scan.js +0 -195
  128. package/dist/infrastructure/security/trust-scorer.js +0 -724
  129. package/dist/infrastructure/security/vibe-checks.js +0 -99
  130. package/dist/infrastructure/storage/atomic-write.js +0 -86
  131. package/dist/infrastructure/storage/cross-tool-agents.js +0 -268
  132. package/dist/infrastructure/storage/project-thread-migration.js +0 -237
  133. package/dist/infrastructure/storage/project-thread-mirror.js +0 -671
  134. package/dist/infrastructure/storage/project-thread-reader.js +0 -409
  135. package/dist/infrastructure/storage/project-thread-title-backfill.js +0 -214
  136. package/dist/monitoring.js +0 -212
  137. package/dist/presentation/cli/commands/audit-command.js +0 -201
  138. package/dist/presentation/cli/commands/budget-command.js +0 -79
  139. package/dist/presentation/cli/commands/config-command.js +0 -125
  140. package/dist/presentation/cli/commands/context-source-command.js +0 -217
  141. package/dist/presentation/cli/commands/hook-command.js +0 -265
  142. package/dist/presentation/cli/commands/init-command.js +0 -590
  143. package/dist/presentation/cli/commands/memory-command.js +0 -1031
  144. package/dist/presentation/cli/commands/narrator-command.js +0 -101
  145. package/dist/presentation/cli/commands/permission-command.js +0 -105
  146. package/dist/presentation/cli/commands/protocols-builder.js +0 -254
  147. package/dist/presentation/cli/commands/protocols-command.js +0 -84
  148. package/dist/presentation/cli/commands/repo-map-builder.js +0 -247
  149. package/dist/presentation/cli/commands/report-command.js +0 -403
  150. package/dist/presentation/cli/commands/scan-command.js +0 -52
  151. package/dist/presentation/cli/commands/scan-tool-command.js +0 -110
  152. package/dist/presentation/cli/commands/search-command.js +0 -51
  153. package/dist/presentation/cli/commands/setup-command.js +0 -330
  154. package/dist/presentation/cli/commands/status-command.js +0 -614
  155. package/dist/presentation/cli/commands/team-command.js +0 -256
  156. package/dist/presentation/cli/commands/thread-command.js +0 -370
  157. package/dist/presentation/cli/commands/trust-command.js +0 -328
  158. package/dist/presentation/cli/commands/verify-command.js +0 -298
  159. package/dist/presentation/cli/error-display.js +0 -328
  160. package/dist/presentation/cli/repl.js +0 -805
  161. package/dist/presentation/cli/task-deliverable.js +0 -153
  162. package/dist/testing/cassette.js +0 -125
  163. package/dist/testing/replay-adapter.js +0 -147
@@ -1,174 +0,0 @@
1
- import { writeFileAtomic } from '../storage/atomic-write.js';
2
- /**
3
- * Configuration Manager
4
- * Manages user preferences and settings for Codehere
5
- * Stores configuration in data/config.json
6
- */
7
- import { readFileSync, existsSync, mkdirSync } from 'fs';
8
- import { join } from 'path';
9
- import { getVersion } from '../../utils/version.js';
10
- function detectDefaultProvider() {
11
- if (process.env.ANTHROPIC_API_KEY)
12
- return { provider: 'anthropic', model: 'claude-fable-5' };
13
- if (process.env.OPENAI_API_KEY)
14
- return { provider: 'openai', model: 'gpt-4o-mini' };
15
- if (process.env.COHERE_API_KEY)
16
- return { provider: 'cohere', model: 'command-r-08-2024' };
17
- if (process.env.CODEHERE_AI_PROVIDER === 'local')
18
- return { provider: 'local', model: 'llama3' };
19
- return { provider: 'auto', model: 'auto' };
20
- }
21
- const DEFAULT_CONFIG = {
22
- version: getVersion(),
23
- preferences: {
24
- autoCommit: false,
25
- askBeforeDestructive: true,
26
- enableCrossTeamLearning: false,
27
- defaultModel: detectDefaultProvider().model,
28
- maxTokens: 4096,
29
- temperature: 0.3,
30
- enableStreaming: true,
31
- },
32
- codebase: {},
33
- api: {
34
- provider: detectDefaultProvider().provider,
35
- model: detectDefaultProvider().model,
36
- rateLimitHandling: 'retry',
37
- },
38
- ui: {
39
- showProgress: true,
40
- showSummary: true,
41
- outputFormat: 'full',
42
- },
43
- };
44
- export class ConfigManager {
45
- configPath;
46
- config;
47
- constructor(configDir) {
48
- const baseDir = configDir || process.cwd();
49
- this.configPath = join(baseDir, 'data', 'config.json');
50
- this.config = this.loadConfig();
51
- }
52
- /**
53
- * Load configuration from file, creating defaults if not exists
54
- */
55
- loadConfig() {
56
- // Ensure data directory exists
57
- const dataDir = join(process.cwd(), 'data');
58
- if (!existsSync(dataDir)) {
59
- mkdirSync(dataDir, { recursive: true });
60
- }
61
- // Load existing config or use defaults
62
- if (existsSync(this.configPath)) {
63
- try {
64
- const fileContent = readFileSync(this.configPath, 'utf-8');
65
- const loaded = JSON.parse(fileContent);
66
- // Merge with defaults to ensure all fields exist
67
- return {
68
- ...DEFAULT_CONFIG,
69
- ...loaded,
70
- preferences: {
71
- ...DEFAULT_CONFIG.preferences,
72
- ...loaded.preferences,
73
- },
74
- codebase: {
75
- ...DEFAULT_CONFIG.codebase,
76
- ...loaded.codebase,
77
- },
78
- api: {
79
- ...DEFAULT_CONFIG.api,
80
- ...loaded.api,
81
- },
82
- ui: {
83
- ...DEFAULT_CONFIG.ui,
84
- ...loaded.ui,
85
- },
86
- };
87
- }
88
- catch (error) {
89
- console.warn(`Failed to load config from ${this.configPath}, using defaults:`, error);
90
- return DEFAULT_CONFIG;
91
- }
92
- }
93
- return DEFAULT_CONFIG;
94
- }
95
- /**
96
- * Save configuration to file
97
- */
98
- saveConfig() {
99
- try {
100
- const dataDir = join(process.cwd(), 'data');
101
- if (!existsSync(dataDir)) {
102
- mkdirSync(dataDir, { recursive: true });
103
- }
104
- writeFileAtomic(this.configPath, JSON.stringify(this.config, null, 2));
105
- }
106
- catch (error) {
107
- throw new Error(`Failed to save config to ${this.configPath}: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
108
- }
109
- }
110
- /**
111
- * Get configuration value
112
- */
113
- get(key) {
114
- return this.config[key];
115
- }
116
- /**
117
- * Get preference value
118
- */
119
- getPreference(key) {
120
- return this.config.preferences[key];
121
- }
122
- /**
123
- * Set configuration value
124
- */
125
- set(key, value) {
126
- this.config[key] = value;
127
- }
128
- /**
129
- * Set preference value
130
- */
131
- setPreference(key, value) {
132
- this.config.preferences[key] = value;
133
- }
134
- /**
135
- * Update codebase information
136
- */
137
- updateCodebase(root, indexedAt) {
138
- this.config.codebase.root = root;
139
- if (indexedAt) {
140
- this.config.codebase.indexedAt = indexedAt;
141
- }
142
- this.config.codebase.lastIndexed = new Date().toISOString();
143
- }
144
- /**
145
- * Get full configuration
146
- */
147
- getAll() {
148
- return { ...this.config };
149
- }
150
- /**
151
- * Reset to defaults
152
- */
153
- reset() {
154
- this.config = DEFAULT_CONFIG;
155
- this.saveConfig();
156
- }
157
- /**
158
- * Get config file path
159
- */
160
- getConfigPath() {
161
- return this.configPath;
162
- }
163
- }
164
- /**
165
- * Get global config manager instance
166
- */
167
- let globalConfigManager = null;
168
- export function getConfigManager(configDir) {
169
- if (!globalConfigManager) {
170
- globalConfigManager = new ConfigManager(configDir);
171
- }
172
- return globalConfigManager;
173
- }
174
- //# sourceMappingURL=config-manager.js.map
@@ -1,49 +0,0 @@
1
- /**
2
- * Product Context
3
- *
4
- * Stores a lightweight "product brain" for the current project.
5
- * Written by `codehere product init`, read by trust/report/router
6
- * to give context-aware recommendations.
7
- *
8
- * Stored in: .codehere/product.json (per-project, gitignore-friendly)
9
- */
10
- import { readFileSync, existsSync } from 'fs';
11
- import { join } from 'path';
12
- const CONFIG_DIR = '.codehere';
13
- const CONFIG_FILE = 'product.json';
14
- function getConfigPath(cwd) {
15
- return join(cwd, CONFIG_DIR, CONFIG_FILE);
16
- }
17
- export function loadProductContext(cwd = process.cwd()) {
18
- const path = getConfigPath(cwd);
19
- if (!existsSync(path))
20
- return null;
21
- try {
22
- return JSON.parse(readFileSync(path, 'utf-8'));
23
- }
24
- catch {
25
- return null;
26
- }
27
- }
28
- /**
29
- * Adjusts a trust recommendation based on product context.
30
- * If no context is available, returns the base recommendation unchanged.
31
- */
32
- export function adjustRecommendation(base, criticalCount, highCount, score, ctx) {
33
- if (!ctx)
34
- return base;
35
- const prefix = ctx.productionApp ? `${ctx.name || 'Production app'} · ` : '';
36
- if (criticalCount > 0) {
37
- return `${prefix}${criticalCount} critical issue${criticalCount !== 1 ? 's' : ''} — fix before this goes to production.`;
38
- }
39
- if (ctx.riskLevel === 'critical' || ctx.riskLevel === 'high') {
40
- if (highCount > 0) {
41
- return `${prefix}${highCount} high-severity pattern${highCount !== 1 ? 's' : ''} — worth fixing before merge given the risk level.`;
42
- }
43
- if (score >= 75) {
44
- return `${prefix}Clean scan. Good to merge — double-check ${ctx.sensitiveAreas.slice(0, 2).join(' and ') || 'sensitive areas'} manually.`;
45
- }
46
- }
47
- return base;
48
- }
49
- //# sourceMappingURL=product-context.js.map
@@ -1,157 +0,0 @@
1
- /**
2
- * draw.io context-source adapter (v0.8 S5 · third reference adapter).
3
- *
4
- * URL shapes accepted (we route both via the same scheme to keep the
5
- * agent prompt forgiving):
6
- * - `drawio://<gist-or-raw-url-base64>` · base64url-encoded source URL
7
- * - `drawio://gh/<owner>/<repo>/<path>` · raw GitHub-hosted .drawio XML
8
- * - `drawio://url/<urlencoded-https-url>` · arbitrary HTTPS-hosted .drawio
9
- *
10
- * No OAuth · draw.io is a client-side editor, the file itself is the
11
- * payload. We fetch the raw XML, summarize node + edge counts, and emit
12
- * the first ~3KB of the XML so the agent can read structure without us
13
- * doing graphviz-style parsing here. Bigger diagrams get a `...truncated`
14
- * suffix; agents can ask the human for a screenshot if they need pixels.
15
- *
16
- * Local-first: no codehere endpoint is contacted. Network call goes
17
- * directly to GitHub-raw / the supplied HTTPS host. Audit chain records
18
- * fetch + byte count, never content.
19
- */
20
- import { ContextSourceError, estimateTokens, } from '../context-source.js';
21
- const MAX_XML_CHARS = 3000;
22
- const MAX_RESPONSE_BYTES = 1_500_000; // 1.5MB · refuse anything bigger to protect prompt budget
23
- /** Turn the codehere drawio:// URL into a concrete HTTPS fetch target. */
24
- function resolveTarget(url) {
25
- const raw = url.toString();
26
- const after = raw.slice('drawio://'.length).replace(/^\/+/, '');
27
- if (!after) {
28
- throw new ContextSourceError('drawio', 'invalid_url', 'drawio URL missing path');
29
- }
30
- // gh/<owner>/<repo>/<path...>
31
- if (after.startsWith('gh/')) {
32
- const parts = after.slice('gh/'.length).split('/');
33
- if (parts.length < 3) {
34
- throw new ContextSourceError('drawio', 'invalid_url', 'gh/ path needs <owner>/<repo>/<path>');
35
- }
36
- const [owner, repo, ...rest] = parts;
37
- const path = rest.join('/');
38
- if (!/^[A-Za-z0-9._-]+$/.test(owner) || !/^[A-Za-z0-9._-]+$/.test(repo)) {
39
- throw new ContextSourceError('drawio', 'invalid_url', 'gh/ owner/repo must be alphanumeric');
40
- }
41
- return {
42
- fetchUrl: `https://raw.githubusercontent.com/${owner}/${repo}/HEAD/${path}`,
43
- label: `github.com/${owner}/${repo}/${path}`,
44
- };
45
- }
46
- // url/<urlencoded-https-url>
47
- if (after.startsWith('url/')) {
48
- const encoded = after.slice('url/'.length);
49
- const decoded = safeDecode(encoded);
50
- if (!/^https:\/\//.test(decoded)) {
51
- throw new ContextSourceError('drawio', 'invalid_url', 'url/ target must be https://');
52
- }
53
- return { fetchUrl: decoded, label: decoded };
54
- }
55
- // base64url-encoded full URL
56
- const decoded = safeBase64UrlDecode(after);
57
- if (decoded && /^https:\/\//.test(decoded)) {
58
- return { fetchUrl: decoded, label: decoded };
59
- }
60
- throw new ContextSourceError('drawio', 'invalid_url', 'use drawio://gh/<owner>/<repo>/<path> · drawio://url/<urlencoded-https> · or drawio://<base64url-https>');
61
- }
62
- function safeDecode(s) {
63
- try {
64
- return decodeURIComponent(s);
65
- }
66
- catch {
67
- return '';
68
- }
69
- }
70
- function safeBase64UrlDecode(s) {
71
- try {
72
- const pad = '='.repeat((4 - (s.length % 4)) % 4);
73
- return Buffer.from(s.replace(/-/g, '+').replace(/_/g, '/') + pad, 'base64').toString('utf-8');
74
- }
75
- catch {
76
- return '';
77
- }
78
- }
79
- export const drawioSource = {
80
- id: 'drawio',
81
- label: 'draw.io',
82
- scheme: 'drawio',
83
- description: 'Fetch a draw.io / diagrams.net diagram XML (architecture, flowchart, etc.)',
84
- isConfigured() {
85
- // Public-by-URL · no auth needed. Always available.
86
- return true;
87
- },
88
- async fetch(url, ctx) {
89
- const target = resolveTarget(url);
90
- const res = await ctx.httpGet(target.fetchUrl, { 'User-Agent': 'codehere/0.8' });
91
- if (res.status === 404) {
92
- throw new ContextSourceError('drawio', 'not_found', `diagram not found at ${target.label}`);
93
- }
94
- if (res.status === 429) {
95
- throw new ContextSourceError('drawio', 'rate_limit', `host returned 429 for ${target.label}`);
96
- }
97
- if (res.status !== 200) {
98
- throw new ContextSourceError('drawio', 'network', `host returned ${res.status} for ${target.label}: ${res.body.slice(0, 200)}`);
99
- }
100
- if (res.body.length > MAX_RESPONSE_BYTES) {
101
- throw new ContextSourceError('drawio', 'parse', `diagram too big (${res.body.length} bytes · limit ${MAX_RESPONSE_BYTES})`);
102
- }
103
- if (!looksLikeDrawio(res.body)) {
104
- throw new ContextSourceError('drawio', 'parse', 'response does not look like a draw.io XML or SVG file');
105
- }
106
- const stats = quickStats(res.body);
107
- const text = renderDiagramAsPlainText(target, res.body, stats);
108
- return {
109
- url: url.toString(),
110
- source: 'drawio',
111
- fetchedAt: new Date(ctx.now ? ctx.now() : Date.now()).toISOString(),
112
- text,
113
- estimatedTokens: estimateTokens(text),
114
- metadata: {
115
- label: target.label,
116
- bytes: res.body.length,
117
- cellCount: stats.cellCount,
118
- edgeCount: stats.edgeCount,
119
- pageCount: stats.pageCount,
120
- },
121
- };
122
- },
123
- };
124
- /** Does this payload look like a draw.io diagram (XML) or SVG export?
125
- * Accept any prolog (whitespace, BOM, `<?xml ...?>`) before the first
126
- * meaningful tag · `<mxfile`, `<mxGraphModel`, or `<svg`. */
127
- function looksLikeDrawio(body) {
128
- const trimmed = body.replace(/^\uFEFF/, '').trimStart();
129
- const stripped = trimmed.startsWith('<?') ? trimmed.slice(trimmed.indexOf('?>') + 2).trimStart() : trimmed;
130
- return /^<(mxfile|mxGraphModel|svg)\b/i.test(stripped);
131
- }
132
- /** Cheap regex stats · we don't ship a real XML parser. */
133
- function quickStats(xml) {
134
- const cellMatches = xml.match(/<mxCell\b/g);
135
- const edgeMatches = xml.match(/<mxCell\b[^>]*\bedge="1"/g);
136
- const pageMatches = xml.match(/<diagram\b/g);
137
- return {
138
- cellCount: cellMatches ? cellMatches.length : 0,
139
- edgeCount: edgeMatches ? edgeMatches.length : 0,
140
- pageCount: pageMatches ? pageMatches.length : 1,
141
- };
142
- }
143
- function renderDiagramAsPlainText(target, xml, stats) {
144
- const lines = [];
145
- lines.push(`draw.io diagram · ${target.label}`);
146
- lines.push('');
147
- lines.push(`structure: ${stats.cellCount} cells · ${stats.edgeCount} edges · ${stats.pageCount} page${stats.pageCount === 1 ? '' : 's'}`);
148
- lines.push(`bytes: ${xml.length}`);
149
- lines.push('');
150
- lines.push('--- raw xml (truncated) ---');
151
- lines.push(xml.slice(0, MAX_XML_CHARS));
152
- if (xml.length > MAX_XML_CHARS) {
153
- lines.push(`...[truncated · diagram is ${xml.length} chars · ask the human for a screenshot if structure isn't clear]`);
154
- }
155
- return lines.join('\n');
156
- }
157
- //# sourceMappingURL=drawio-source.js.map
@@ -1,198 +0,0 @@
1
- /**
2
- * Figma context-source adapter (v0.8 S1 · second reference adapter).
3
- *
4
- * URL shapes:
5
- * figma://file/<file-key> · whole-file metadata + top-level frames
6
- * figma://file/<file-key>/node/<node-id> · one node + its children, flattened
7
- *
8
- * Returns the design tree as indented plain text: frame names, types,
9
- * dimensions, text content of TEXT nodes, fill colors when they're solid.
10
- * Generated previews + raster bytes are skipped · agents read text, not
11
- * pixels (overlay-renderer in v0.9 will surface raw frames separately).
12
- *
13
- * Auth: ctx.config['figma'] = { token: '<personal-access-token>' }
14
- * Token comes from figma.com/developers/api · personal access token.
15
- */
16
- import { ContextSourceError, estimateTokens, } from '../context-source.js';
17
- const FIGMA_API = 'https://api.figma.com/v1';
18
- const MAX_TREE_NODES = 200;
19
- const MAX_TEXT_PER_NODE = 240;
20
- function parsePath(url) {
21
- // figma://file/<key> · hostname = 'file', pathname = '/<key>'
22
- // figma://file/<key>/node/<id>
23
- const host = url.host || url.hostname; // 'file'
24
- if (host !== 'file') {
25
- throw new ContextSourceError('figma', 'invalid_url', `URL must start with figma://file/<key>; got host='${host}'`);
26
- }
27
- const parts = url.pathname.split('/').filter(Boolean);
28
- if (!parts.length) {
29
- throw new ContextSourceError('figma', 'invalid_url', 'missing file key in figma:// URL');
30
- }
31
- const fileKey = parts[0];
32
- let nodeId = null;
33
- if (parts.length >= 3 && parts[1] === 'node') {
34
- nodeId = decodeURIComponent(parts.slice(2).join('/'));
35
- }
36
- return { fileKey, nodeId };
37
- }
38
- export const figmaSource = {
39
- id: 'figma',
40
- label: 'Figma',
41
- scheme: 'figma',
42
- description: 'Fetch a Figma file or single node (tree, dimensions, text content)',
43
- isConfigured(ctx) {
44
- const cfg = ctx.config['figma'];
45
- if (!cfg)
46
- return false;
47
- if (cfg.oauth?.accessToken)
48
- return true;
49
- return typeof cfg.token === 'string' && cfg.token.length > 8;
50
- },
51
- async fetch(url, ctx) {
52
- const cfg = ctx.config['figma'] ?? {};
53
- // Figma supports BOTH a personal-access token (X-Figma-Token header) AND
54
- // an OAuth bearer (Authorization: Bearer ...) · pick whichever is set.
55
- let headers;
56
- if (cfg.oauth?.accessToken) {
57
- headers = { Authorization: `Bearer ${cfg.oauth.accessToken}` };
58
- }
59
- else if (cfg.token) {
60
- headers = { 'X-Figma-Token': cfg.token };
61
- }
62
- else {
63
- throw new ContextSourceError('figma', 'auth', 'no token or OAuth in ctx.config["figma"]');
64
- }
65
- const { fileKey, nodeId } = parsePath(url);
66
- const endpoint = cfg.endpoint ?? FIGMA_API;
67
- let res;
68
- if (nodeId) {
69
- const target = `${endpoint}/files/${encodeURIComponent(fileKey)}/nodes?ids=${encodeURIComponent(nodeId)}`;
70
- res = await ctx.httpGet(target, headers);
71
- }
72
- else {
73
- const target = `${endpoint}/files/${encodeURIComponent(fileKey)}?depth=2`;
74
- res = await ctx.httpGet(target, headers);
75
- }
76
- if (res.status === 401 || res.status === 403) {
77
- throw new ContextSourceError('figma', 'auth', `Figma returned ${res.status}`);
78
- }
79
- if (res.status === 404) {
80
- throw new ContextSourceError('figma', 'not_found', `file ${fileKey} not found`);
81
- }
82
- if (res.status === 429) {
83
- throw new ContextSourceError('figma', 'rate_limit', 'Figma rate limit hit');
84
- }
85
- if (res.status !== 200) {
86
- throw new ContextSourceError('figma', 'network', `Figma returned ${res.status}: ${res.body.slice(0, 200)}`);
87
- }
88
- let text;
89
- let metadata;
90
- if (nodeId) {
91
- const parsed = JSON.parse(res.body);
92
- if (parsed.err)
93
- throw new ContextSourceError('figma', 'parse', `Figma error: ${parsed.err}`);
94
- const node = parsed.nodes?.[nodeId]?.document;
95
- if (!node)
96
- throw new ContextSourceError('figma', 'not_found', `node ${nodeId} not in response`);
97
- text = renderNodeAsTree(node, fileKey, nodeId);
98
- metadata = { fileKey, nodeId, type: node.type, name: node.name, frames: collectFrames(node) };
99
- }
100
- else {
101
- const parsed = JSON.parse(res.body);
102
- if (parsed.err)
103
- throw new ContextSourceError('figma', 'parse', `Figma error: ${parsed.err}`);
104
- if (!parsed.document)
105
- throw new ContextSourceError('figma', 'parse', 'Figma response missing document');
106
- text = renderFile(parsed, fileKey);
107
- metadata = {
108
- fileKey,
109
- name: parsed.name ?? null,
110
- lastModified: parsed.lastModified ?? null,
111
- frames: collectFrames(parsed.document),
112
- };
113
- }
114
- return {
115
- url: url.toString(),
116
- source: 'figma',
117
- fetchedAt: new Date(ctx.now ? ctx.now() : Date.now()).toISOString(),
118
- text,
119
- estimatedTokens: estimateTokens(text),
120
- metadata,
121
- };
122
- },
123
- };
124
- function collectFrames(root, limit = 12) {
125
- const frames = [];
126
- const visit = (node) => {
127
- if (frames.length >= limit)
128
- return;
129
- if (node.type === 'FRAME') {
130
- const bb = node.absoluteBoundingBox;
131
- frames.push({
132
- name: node.name,
133
- width: bb ? Math.round(bb.width) : null,
134
- height: bb ? Math.round(bb.height) : null,
135
- text: firstText(node),
136
- });
137
- }
138
- for (const child of node.children ?? [])
139
- visit(child);
140
- };
141
- visit(root);
142
- return frames;
143
- }
144
- function firstText(node) {
145
- if (node.characters)
146
- return node.characters.replace(/\s+/g, ' ').slice(0, MAX_TEXT_PER_NODE);
147
- for (const child of node.children ?? []) {
148
- const t = firstText(child);
149
- if (t)
150
- return t;
151
- }
152
- return null;
153
- }
154
- function renderFile(file, fileKey) {
155
- const lines = [];
156
- lines.push(`Figma file · ${file.name ?? '(unnamed)'}`);
157
- lines.push(`https://www.figma.com/file/${fileKey}`);
158
- if (file.lastModified)
159
- lines.push(`updated: ${file.lastModified}`);
160
- lines.push('');
161
- if (file.document) {
162
- lines.push('--- top-level tree (depth 2) ---');
163
- renderInto(lines, file.document, 0, { remaining: MAX_TREE_NODES });
164
- }
165
- return lines.join('\n');
166
- }
167
- function renderNodeAsTree(node, fileKey, nodeId) {
168
- const lines = [];
169
- lines.push(`Figma node · ${node.name} (${node.type})`);
170
- lines.push(`https://www.figma.com/file/${fileKey}?node-id=${encodeURIComponent(nodeId)}`);
171
- lines.push('');
172
- renderInto(lines, node, 0, { remaining: MAX_TREE_NODES });
173
- return lines.join('\n');
174
- }
175
- function renderInto(out, node, depth, budget) {
176
- if (budget.remaining <= 0) {
177
- if (budget.remaining === 0) {
178
- out.push('...[tree truncated · node budget reached]');
179
- budget.remaining = -1;
180
- }
181
- return;
182
- }
183
- budget.remaining -= 1;
184
- const indent = ' '.repeat(depth);
185
- const bb = node.absoluteBoundingBox;
186
- const dim = bb ? ` ${Math.round(bb.width)}×${Math.round(bb.height)}` : '';
187
- const visible = node.visible === false ? ' [hidden]' : '';
188
- let line = `${indent}${node.type} · ${node.name}${dim}${visible}`;
189
- if (node.characters) {
190
- const t = node.characters.replace(/\s+/g, ' ').slice(0, MAX_TEXT_PER_NODE);
191
- line += ` · "${t}${node.characters.length > MAX_TEXT_PER_NODE ? '…' : ''}"`;
192
- }
193
- out.push(line);
194
- for (const child of node.children ?? []) {
195
- renderInto(out, child, depth + 1, budget);
196
- }
197
- }
198
- //# sourceMappingURL=figma-source.js.map