codehere 1.0.1 → 1.0.2

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 +36 -192
  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,160 +0,0 @@
1
- /**
2
- * GitHub Issues context-source adapter (v0.8 S5 · community-shaped reference).
3
- *
4
- * Demonstrates that the context-source primitive isn't Linear/Figma-shaped
5
- * by accident · the same contract serves a no-auth REST API. Any community
6
- * contributor should be able to ship an adapter by copying THIS file, not
7
- * the OAuth-heavy linear-source.ts.
8
- *
9
- * URL shape: `github://<owner>/<repo>/issues/<number>` (or `/pull/<number>`,
10
- * which GitHub treats as an issue under the hood).
11
- *
12
- * Public-issues-only by default · no auth needed. If ctx.config['github']
13
- * provides a `token`, we send it as a Bearer header to raise the rate
14
- * limit + read private repos the token has scopes for.
15
- *
16
- * Returns: title, state, author, body, last N comments. Same shape as
17
- * linear-source so prompts feel uniform across providers.
18
- */
19
- import { ContextSourceError, estimateTokens, } from '../context-source.js';
20
- const GH_API = 'https://api.github.com';
21
- const MAX_BODY_CHARS = 4000;
22
- const MAX_COMMENT_CHARS = 600;
23
- const MAX_COMMENTS = 10;
24
- function parseRef(url) {
25
- // URL: `github://<owner>/<repo>/issues/<number>` or `.../pull/<number>`
26
- const host = url.host || url.hostname;
27
- const path = url.pathname || '';
28
- // Path begins with '/' · split
29
- const parts = [host, ...path.split('/').filter(Boolean)];
30
- if (parts.length < 4) {
31
- throw new ContextSourceError('github', 'invalid_url', 'use github://<owner>/<repo>/issues/<number> or github://<owner>/<repo>/pull/<number>');
32
- }
33
- const [owner, repo, kind, numStr] = parts;
34
- if (!/^[A-Za-z0-9._-]+$/.test(owner) || !/^[A-Za-z0-9._-]+$/.test(repo)) {
35
- throw new ContextSourceError('github', 'invalid_url', 'owner/repo must be alphanumeric');
36
- }
37
- if (kind !== 'issues' && kind !== 'pull') {
38
- throw new ContextSourceError('github', 'invalid_url', `expected /issues/ or /pull/ · got /${kind}/`);
39
- }
40
- const number = Number.parseInt(numStr, 10);
41
- if (!Number.isInteger(number) || number <= 0) {
42
- throw new ContextSourceError('github', 'invalid_url', `not an issue number: '${numStr}'`);
43
- }
44
- return { owner, repo, number };
45
- }
46
- export const githubIssueSource = {
47
- id: 'github',
48
- label: 'GitHub Issues',
49
- scheme: 'github',
50
- description: 'Fetch a public GitHub issue or PR (title, body, recent comments) · no auth needed',
51
- isConfigured() {
52
- // Always available · public issues need no auth.
53
- return true;
54
- },
55
- async fetch(url, ctx) {
56
- const ref = parseRef(url);
57
- const cfg = ctx.config['github'] ?? {};
58
- const endpoint = cfg.endpoint ?? GH_API;
59
- const headers = {
60
- Accept: 'application/vnd.github+json',
61
- 'X-GitHub-Api-Version': '2022-11-28',
62
- 'User-Agent': 'codehere/0.8',
63
- };
64
- const bearer = cfg.token ?? cfg.oauth?.accessToken;
65
- if (bearer)
66
- headers['Authorization'] = `Bearer ${bearer}`;
67
- const issueRes = await ctx.httpGet(`${endpoint}/repos/${ref.owner}/${ref.repo}/issues/${ref.number}`, headers);
68
- if (issueRes.status === 404) {
69
- throw new ContextSourceError('github', 'not_found', `${ref.owner}/${ref.repo}#${ref.number} not found`);
70
- }
71
- // GitHub returns 403 for BOTH rate-limit and auth · disambiguate by body
72
- // sniff first (rate-limit responses say "API rate limit exceeded").
73
- if (issueRes.status === 429 || (issueRes.status === 403 && /rate limit/i.test(issueRes.body))) {
74
- throw new ContextSourceError('github', 'rate_limit', 'GitHub API rate limit hit');
75
- }
76
- if (issueRes.status === 401 || issueRes.status === 403) {
77
- throw new ContextSourceError('github', 'auth', `GitHub returned ${issueRes.status} · public issues should work without a token; private ones need ctx.config["github"].token`);
78
- }
79
- if (issueRes.status !== 200) {
80
- throw new ContextSourceError('github', 'network', `GitHub returned ${issueRes.status}: ${issueRes.body.slice(0, 200)}`);
81
- }
82
- let issue;
83
- try {
84
- issue = JSON.parse(issueRes.body);
85
- }
86
- catch (err) {
87
- throw new ContextSourceError('github', 'parse', `GitHub issue response not JSON: ${err instanceof Error ? err.message : String(err)}`);
88
- }
89
- // Comments are paginated · fetch the most recent page (sort=created direction=desc)
90
- let comments = [];
91
- if ((issue.comments ?? 0) > 0) {
92
- const cRes = await ctx.httpGet(`${endpoint}/repos/${ref.owner}/${ref.repo}/issues/${ref.number}/comments?per_page=${MAX_COMMENTS}&sort=created&direction=desc`, headers);
93
- if (cRes.status === 200) {
94
- try {
95
- comments = JSON.parse(cRes.body).reverse(); // chronological for display
96
- }
97
- catch {
98
- // Non-fatal · we still have the issue body.
99
- comments = [];
100
- }
101
- }
102
- }
103
- const text = renderIssueAsPlainText(ref, issue, comments);
104
- return {
105
- url: url.toString(),
106
- source: 'github',
107
- fetchedAt: new Date(ctx.now ? ctx.now() : Date.now()).toISOString(),
108
- text,
109
- estimatedTokens: estimateTokens(text),
110
- metadata: {
111
- owner: ref.owner,
112
- repo: ref.repo,
113
- number: ref.number,
114
- title: issue.title,
115
- state: issue.state,
116
- author: issue.user?.login ?? null,
117
- html_url: issue.html_url,
118
- commentCount: comments.length,
119
- },
120
- };
121
- },
122
- };
123
- function renderIssueAsPlainText(ref, issue, comments) {
124
- const lines = [];
125
- lines.push(`GitHub ${ref.owner}/${ref.repo}#${issue.number} · ${issue.title}`);
126
- lines.push(issue.html_url);
127
- lines.push('');
128
- lines.push(`state: ${issue.state}`);
129
- if (issue.user?.login)
130
- lines.push(`author: ${issue.user.login}`);
131
- if (issue.assignee?.login)
132
- lines.push(`assignee: ${issue.assignee.login}`);
133
- if (issue.labels?.length) {
134
- const names = issue.labels
135
- .map((l) => l.name)
136
- .filter(Boolean)
137
- .join(', ');
138
- if (names)
139
- lines.push(`labels: ${names}`);
140
- }
141
- lines.push(`updated: ${issue.updated_at}`);
142
- lines.push('');
143
- lines.push('--- body ---');
144
- const body = (issue.body ?? '').slice(0, MAX_BODY_CHARS);
145
- lines.push(body || '(no body)');
146
- if ((issue.body ?? '').length > MAX_BODY_CHARS) {
147
- lines.push(`...[truncated · body is ${issue.body.length} chars]`);
148
- }
149
- if (comments.length) {
150
- lines.push('');
151
- lines.push(`--- last ${comments.length} comment${comments.length === 1 ? '' : 's'} ---`);
152
- for (const c of comments) {
153
- const who = c.user?.login ?? 'unknown';
154
- const body = (c.body ?? '').slice(0, MAX_COMMENT_CHARS).replace(/\s+\n/g, '\n');
155
- lines.push(`[${c.created_at}] ${who}: ${body}${(c.body ?? '').length > MAX_COMMENT_CHARS ? '...' : ''}`);
156
- }
157
- }
158
- return lines.join('\n');
159
- }
160
- //# sourceMappingURL=github-issue-source.js.map
@@ -1,190 +0,0 @@
1
- /**
2
- * Linear context-source adapter (v0.8 S1 · first reference adapter).
3
- *
4
- * URL shape: `linear://<TEAM-KEY>-<NUMBER>` · e.g. `linear://INFRA-247`.
5
- *
6
- * Returns the issue's title, status, assignee, description, and the last N
7
- * comments as plain text. Token usage is bounded · a 5K-char issue with
8
- * a 200-comment thread doesn't get spammed in full · we keep the top of the
9
- * description + the last 10 comments by default.
10
- *
11
- * Auth: ctx.config['linear'] = { apiKey: '<personal-api-key>' }
12
- * Get a key from linear.app/settings/api · personal key OR OAuth-issued token.
13
- *
14
- * Local-first: codehere never stores the issue payload on disk · the agent
15
- * reads it once into prompt context, the audit chain records WHEN we fetched
16
- * and BYTES we got but never the content. If the user wants persistence,
17
- * that's their git tree.
18
- */
19
- import { ContextSourceError, estimateTokens, } from '../context-source.js';
20
- const LINEAR_API = 'https://api.linear.app/graphql';
21
- const MAX_DESCRIPTION_CHARS = 4000;
22
- const MAX_COMMENT_CHARS = 600;
23
- const MAX_COMMENTS = 10;
24
- /** Resolve the auth header value from either OAuth or manual key. */
25
- function authToken(cfg) {
26
- if (cfg.oauth?.accessToken)
27
- return `Bearer ${cfg.oauth.accessToken}`;
28
- if (cfg.apiKey)
29
- return cfg.apiKey;
30
- return null;
31
- }
32
- function parseIdentifier(parsed) {
33
- // URL parses `linear://INFRA-247` with host = 'infra-247' (lowercased).
34
- // The pathname is empty. Reassemble + uppercase the team key.
35
- const host = parsed.host || parsed.hostname;
36
- if (!host)
37
- throw new ContextSourceError('linear', 'invalid_url', 'no issue identifier in URL');
38
- // Linear identifiers are <TEAM>-<NUMBER> · normalize case so callers can
39
- // write `linear://infra-247` and we still hit `INFRA-247`.
40
- const upper = host.toUpperCase();
41
- if (!/^[A-Z][A-Z0-9_]*-\d+$/.test(upper)) {
42
- throw new ContextSourceError('linear', 'invalid_url', `not a Linear identifier: '${host}'`);
43
- }
44
- return upper;
45
- }
46
- const ISSUE_QUERY = `query($id: String!) {
47
- issue(id: $id) {
48
- id identifier title description url priority priorityLabel createdAt updatedAt
49
- state { name type }
50
- assignee { name }
51
- team { key name }
52
- comments(last: ${MAX_COMMENTS}) {
53
- nodes { id body createdAt user { name } }
54
- }
55
- }
56
- }`;
57
- export const linearSource = {
58
- id: 'linear',
59
- label: 'Linear',
60
- scheme: 'linear',
61
- description: 'Fetch a Linear issue (title, status, description, recent comments)',
62
- isConfigured(ctx) {
63
- const cfg = ctx.config['linear'];
64
- if (!cfg)
65
- return false;
66
- if (cfg.oauth?.accessToken)
67
- return true;
68
- return typeof cfg.apiKey === 'string' && cfg.apiKey.length > 8;
69
- },
70
- async fetch(url, ctx) {
71
- const cfg = ctx.config['linear'] ?? {};
72
- const auth = authToken(cfg);
73
- if (!auth) {
74
- throw new ContextSourceError('linear', 'auth', 'no apiKey or OAuth token in ctx.config["linear"]');
75
- }
76
- const identifier = parseIdentifier(url);
77
- const endpoint = cfg.endpoint ?? LINEAR_API;
78
- const headers = {
79
- Authorization: auth,
80
- 'Content-Type': 'application/json',
81
- };
82
- const body = JSON.stringify({ query: ISSUE_QUERY, variables: { id: identifier } });
83
- // ContextSourceFetchContext gives us httpGet; we POST GraphQL via a
84
- // small helper since httpGet is GET-only in the canonical signature.
85
- // For now we go through the same path but with a `body` smuggled in
86
- // via headers · NOT pretty. Future: add httpPost to the context. The
87
- // shape is identical (status, body, headers) so adapters port cleanly.
88
- const httpPost = ctx.httpPost;
89
- let res;
90
- if (httpPost) {
91
- res = await httpPost(endpoint, body, headers);
92
- }
93
- else {
94
- // Production fallback · use fetch directly. Tests inject httpPost so
95
- // they never reach this branch.
96
- const fr = await fetch(endpoint, { method: 'POST', headers, body });
97
- res = {
98
- status: fr.status,
99
- body: await fr.text(),
100
- headers: Object.fromEntries(fr.headers),
101
- };
102
- }
103
- if (res.status === 401 || res.status === 403) {
104
- throw new ContextSourceError('linear', 'auth', `Linear returned ${res.status}`);
105
- }
106
- if (res.status === 404) {
107
- throw new ContextSourceError('linear', 'not_found', `issue ${identifier} not found`);
108
- }
109
- if (res.status === 429) {
110
- throw new ContextSourceError('linear', 'rate_limit', 'Linear rate limit hit');
111
- }
112
- if (res.status !== 200) {
113
- throw new ContextSourceError('linear', 'network', `Linear returned ${res.status}: ${res.body.slice(0, 200)}`);
114
- }
115
- let payload;
116
- try {
117
- payload = JSON.parse(res.body);
118
- }
119
- catch (err) {
120
- throw new ContextSourceError('linear', 'parse', `Linear response not JSON: ${err instanceof Error ? err.message : String(err)}`);
121
- }
122
- if (payload.errors?.length) {
123
- throw new ContextSourceError('linear', 'parse', `Linear GraphQL errors: ${payload.errors.map((e) => e.message).join('; ')}`);
124
- }
125
- const issue = payload.data?.issue;
126
- if (!issue) {
127
- throw new ContextSourceError('linear', 'not_found', `issue ${identifier} returned null`);
128
- }
129
- const text = renderIssueAsPlainText(issue);
130
- return {
131
- url: url.toString(),
132
- source: 'linear',
133
- fetchedAt: new Date(ctx.now ? ctx.now() : Date.now()).toISOString(),
134
- text,
135
- estimatedTokens: estimateTokens(text),
136
- metadata: {
137
- identifier: issue.identifier,
138
- title: issue.title,
139
- url: issue.url,
140
- state: issue.state?.name,
141
- stateType: issue.state?.type,
142
- assignee: issue.assignee?.name ?? null,
143
- priorityLabel: issue.priorityLabel ?? null,
144
- priority: issue.priority,
145
- description: issue.description ?? null,
146
- commentCount: issue.comments?.nodes.length ?? 0,
147
- // Structured comments for the overlay-renderer's Linear inline view.
148
- comments: (issue.comments?.nodes ?? []).map((c) => ({
149
- author: c.user?.name ?? 'unknown',
150
- body: c.body,
151
- createdAt: c.createdAt,
152
- })),
153
- },
154
- };
155
- },
156
- };
157
- function renderIssueAsPlainText(issue) {
158
- const lines = [];
159
- lines.push(`Linear issue ${issue.identifier} · ${issue.title}`);
160
- lines.push(issue.url);
161
- lines.push('');
162
- if (issue.state)
163
- lines.push(`status: ${issue.state.name} (${issue.state.type})`);
164
- if (issue.assignee)
165
- lines.push(`assignee: ${issue.assignee.name}`);
166
- if (issue.priorityLabel)
167
- lines.push(`priority: ${issue.priorityLabel}`);
168
- if (issue.team)
169
- lines.push(`team: ${issue.team.name} (${issue.team.key})`);
170
- lines.push(`updated: ${issue.updatedAt}`);
171
- lines.push('');
172
- lines.push('--- description ---');
173
- const desc = (issue.description ?? '').slice(0, MAX_DESCRIPTION_CHARS);
174
- lines.push(desc || '(no description)');
175
- if ((issue.description ?? '').length > MAX_DESCRIPTION_CHARS) {
176
- lines.push(`...[truncated · description is ${issue.description.length} chars]`);
177
- }
178
- const comments = issue.comments?.nodes ?? [];
179
- if (comments.length) {
180
- lines.push('');
181
- lines.push(`--- last ${comments.length} comment${comments.length === 1 ? '' : 's'} ---`);
182
- for (const c of comments) {
183
- const who = c.user?.name ?? 'unknown';
184
- const body = c.body.slice(0, MAX_COMMENT_CHARS).replace(/\s+\n/g, '\n');
185
- lines.push(`[${c.createdAt}] ${who}: ${body}${c.body.length > MAX_COMMENT_CHARS ? '...' : ''}`);
186
- }
187
- }
188
- return lines.join('\n');
189
- }
190
- //# sourceMappingURL=linear-source.js.map
@@ -1,144 +0,0 @@
1
- /**
2
- * Context resolver · v0.8 S2 · integrates context-sources into the spawn path.
3
- *
4
- * What it does: scans the user's prompt for any context-source URL
5
- * (linear://..., figma://..., draw.io://..., etc.), resolves each one via
6
- * the registry, and returns an enriched prompt with the resolved content
7
- * appended as a structured preamble. Audit lines emit on every resolve.
8
- *
9
- * Why a separate module: the registry is pure data and the adapters are
10
- * pure async functions, but spawn() is synchronous-feeling code. This module
11
- * is the seam where we wait on async fetches before the agent starts.
12
- *
13
- * Two principles:
14
- * 1. Best-effort by default. If one URL fails (auth, network, 404), we log
15
- * the error inline in the preamble and CONTINUE with the others rather
16
- * than failing the whole spawn. The agent still gets the user's prompt
17
- * + whatever resolved; the audit chain still gets every error.
18
- * 2. The resolved content goes into the prompt as a `[context-source ...]`
19
- * block, NOT as a tool-call result. Tool-call wiring belongs in the
20
- * adapters themselves; this layer is a pre-prompt enrichment.
21
- */
22
- import { fetchViaRegistry, } from './context-source.js';
23
- /* ──────────────────────────────────────────────────────────────────────── */
24
- /* URL extraction */
25
- /* ──────────────────────────────────────────────────────────────────────── */
26
- /**
27
- * Extract every URL whose scheme is registered in the given registry. We do
28
- * NOT extract `http(s)` URLs · those are usually documentation links, not
29
- * something to inline.
30
- *
31
- * Scheme matching: case-insensitive; URLs can appear inline mid-sentence
32
- * surrounded by punctuation. We tokenize loosely (whitespace + a few
33
- * trailing punctuation chars) so `(linear://INFRA-247).` doesn't include
34
- * the period or paren.
35
- */
36
- export function extractContextSourceUrls(prompt, reg) {
37
- if (!prompt)
38
- return [];
39
- const schemes = Array.from(reg.bySchemeOnly.keys());
40
- if (!schemes.length)
41
- return [];
42
- // Build a regex that anchors on any registered scheme.
43
- const schemePart = schemes.map(escapeRe).join('|');
44
- // Tokens: <scheme>://<chars-until-trailing-punct-or-whitespace>
45
- const re = new RegExp(`\\b(${schemePart})://[^\\s<>"\`]+`, 'gi');
46
- const seen = new Set();
47
- const out = [];
48
- for (const match of prompt.matchAll(re)) {
49
- // Strip trailing punctuation a user is likely to include accidentally.
50
- const cleaned = match[0].replace(/[.,;:!?)\]}]+$/, '');
51
- if (!seen.has(cleaned)) {
52
- seen.add(cleaned);
53
- out.push(cleaned);
54
- }
55
- }
56
- return out;
57
- }
58
- function escapeRe(s) {
59
- return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
60
- }
61
- /**
62
- * Walk the prompt, resolve every registered URL in parallel, append the
63
- * payloads as a preamble. Best-effort: any single failure is logged in the
64
- * preamble and in the audit chain; the spawn proceeds.
65
- */
66
- export async function resolveContextSourceUrls(prompt, reg, ctx) {
67
- const urls = extractContextSourceUrls(prompt, reg);
68
- if (urls.length === 0) {
69
- return { enrichedPrompt: prompt, outcomes: [], totalEstimatedTokens: 0 };
70
- }
71
- // Resolve in parallel · network-bound, no shared state, safe to fanout.
72
- // Cap fanout to a small number to avoid hammering one provider when a
73
- // user pastes 50 Linear URLs at once.
74
- const MAX_PARALLEL = 4;
75
- const outcomes = [];
76
- const payloads = [];
77
- for (let i = 0; i < urls.length; i += MAX_PARALLEL) {
78
- const batch = urls.slice(i, i + MAX_PARALLEL);
79
- const settled = await Promise.allSettled(batch.map((u) => fetchViaRegistry(reg, u, ctx).then((p) => ({ url: u, payload: p }))));
80
- for (let j = 0; j < settled.length; j++) {
81
- const r = settled[j];
82
- const url = batch[j];
83
- if (r.status === 'fulfilled') {
84
- const { payload } = r.value;
85
- if (payload === null) {
86
- outcomes.push({ url, status: 'unknown_scheme' });
87
- }
88
- else {
89
- outcomes.push({
90
- url,
91
- status: 'ok',
92
- source: payload.source,
93
- bytes: payload.text.length,
94
- estimatedTokens: payload.estimatedTokens,
95
- });
96
- payloads.push({ url, payload });
97
- }
98
- }
99
- else {
100
- const err = r.reason;
101
- const isUnconfigured = /not configured/i.test(err.message);
102
- outcomes.push({
103
- url,
104
- status: isUnconfigured ? 'unconfigured' : 'error',
105
- detail: err.message,
106
- });
107
- }
108
- }
109
- }
110
- const totalEstimatedTokens = payloads.reduce((sum, p) => sum + p.payload.estimatedTokens, 0);
111
- const enrichedPrompt = buildEnrichedPrompt(prompt, payloads, outcomes);
112
- return { enrichedPrompt, outcomes, totalEstimatedTokens };
113
- }
114
- function buildEnrichedPrompt(prompt, payloads, outcomes) {
115
- if (payloads.length === 0 && outcomes.every((o) => o.status === 'ok' || o.status === 'unknown_scheme')) {
116
- // Nothing resolved AND no errors worth reporting · skip the preamble entirely.
117
- return prompt;
118
- }
119
- const lines = [];
120
- lines.push('--- context resolved by codehere ---');
121
- lines.push('The user referenced these external resources. Their content is included below for your reference.');
122
- lines.push('');
123
- for (const { url, payload } of payloads) {
124
- lines.push(`[context-source ${payload.source} · ${url}]`);
125
- lines.push(payload.text);
126
- lines.push('');
127
- }
128
- // Report errors inline so the agent knows what it's missing rather than
129
- // silently working without it.
130
- const errors = outcomes.filter((o) => o.status === 'error' || o.status === 'unconfigured');
131
- if (errors.length) {
132
- lines.push('--- context-source errors ---');
133
- for (const e of errors) {
134
- lines.push(`[${e.status}] ${e.url} · ${e.detail ?? '(no detail)'}`);
135
- }
136
- lines.push('');
137
- }
138
- lines.push('--- end of context-source preamble ---');
139
- lines.push('');
140
- lines.push('User prompt below this line:');
141
- lines.push(prompt);
142
- return lines.join('\n');
143
- }
144
- //# sourceMappingURL=context-resolver.js.map
@@ -1,114 +0,0 @@
1
- /**
2
- * Context-source primitive (v0.8 S1).
3
- *
4
- * The "external tool" abstraction codehere ships instead of building per-tool
5
- * integrations. Each adapter takes a URL like `linear://INFRA-247` or
6
- * `figma://team/api-design/frame/429-response` and returns a `ContextPayload`
7
- * the spawned coding agent can read as plain text. Routing is by URL scheme;
8
- * registry is pure data (a Map), so tests can inject mocks without monkey-
9
- * patching globals.
10
- *
11
- * Two principles · captured in chat 2026-05-22:
12
- * 1. Composable primitive · ship 2-3 reference adapters · community fills
13
- * the long tail (no enterprise integration matrix).
14
- * 2. Local-first · NEVER cloud-realtime. Fetch is a read · writes go
15
- * through the W-PASS gate in v0.9.
16
- *
17
- * Pure function shape:
18
- * - createRegistry() builds an empty registry.
19
- * - register(reg, source) appends a source · returns the new registry (or
20
- * mutates in place; both are accepted, see api).
21
- * - resolve(reg, url) picks the source that handles the URL's scheme · or
22
- * undefined if none. No throw.
23
- * - fetch(source, url, ctx) actually invokes the adapter and returns a
24
- * ContextPayload. The only path that does I/O. Caller passes ctx with
25
- * network/auth callbacks · the adapter never touches process.env or
26
- * fetch() directly · easier to mock + safer per local-first.
27
- */
28
- export class ContextSourceError extends Error {
29
- source;
30
- kind;
31
- constructor(source, kind, message) {
32
- super(`[${source}] ${kind}: ${message}`);
33
- this.source = source;
34
- this.kind = kind;
35
- }
36
- }
37
- export function createRegistry() {
38
- return { bySchemeOnly: new Map() };
39
- }
40
- /**
41
- * Register a source. Returns the (mutated) registry so callers can chain.
42
- * Throws if the scheme is already claimed · two adapters on one scheme
43
- * would silently shadow each other, which is exactly the class of bug
44
- * the verifier-agent is meant to catch elsewhere; don't introduce it here.
45
- */
46
- export function register(reg, source) {
47
- if (reg.bySchemeOnly.has(source.scheme)) {
48
- const existing = reg.bySchemeOnly.get(source.scheme);
49
- throw new Error(`context-source: scheme '${source.scheme}' already registered by '${existing.id}'; cannot also register '${source.id}'`);
50
- }
51
- reg.bySchemeOnly.set(source.scheme, source);
52
- return reg;
53
- }
54
- /**
55
- * Resolve a URL to a source by scheme. Returns undefined when nothing matches
56
- * (don't throw · the caller's job to decide if "no adapter" is a user-visible
57
- * error).
58
- */
59
- export function resolve(reg, url) {
60
- let parsed;
61
- try {
62
- parsed = typeof url === 'string' ? new URL(url) : url;
63
- }
64
- catch {
65
- return undefined;
66
- }
67
- // URL.protocol includes the trailing ':' · strip for registry lookup.
68
- const scheme = parsed.protocol.replace(/:$/, '').toLowerCase();
69
- const source = reg.bySchemeOnly.get(scheme);
70
- if (!source)
71
- return undefined;
72
- return { source, parsed };
73
- }
74
- /**
75
- * Fetch via the registry. Returns null when the URL has no registered adapter
76
- * (so callers can distinguish "no adapter" from "fetch failed").
77
- */
78
- export async function fetchViaRegistry(reg, url, ctx) {
79
- const r = resolve(reg, url);
80
- if (!r)
81
- return null;
82
- const ts = ctx.now ? ctx.now() : Date.now();
83
- try {
84
- if (r.source.isConfigured && !r.source.isConfigured(ctx)) {
85
- ctx.emitAuditLine?.({ source: r.source.id, url, status: 'unconfigured', ts });
86
- throw new ContextSourceError(r.source.id, 'auth', `adapter '${r.source.id}' is not configured · set ctx.config['${r.source.id}'] (e.g., token, base URL)`);
87
- }
88
- const payload = await r.source.fetch(r.parsed, ctx);
89
- ctx.emitAuditLine?.({ source: r.source.id, url, status: 'ok', ts, bytes: payload.text.length });
90
- return payload;
91
- }
92
- catch (err) {
93
- const detail = err instanceof Error ? err.message : String(err);
94
- ctx.emitAuditLine?.({ source: r.source.id, url, status: 'error', ts, detail });
95
- throw err;
96
- }
97
- }
98
- /** List configured sources for UI rendering. */
99
- export function listAvailable(reg, ctx) {
100
- return Array.from(reg.bySchemeOnly.values()).filter((s) => {
101
- if (!s.isConfigured)
102
- return true; // adapter doesn't gate · assume usable
103
- return s.isConfigured(ctx);
104
- });
105
- }
106
- /** List ALL sources regardless of config · for `codehere context-source list`. */
107
- export function listAll(reg) {
108
- return Array.from(reg.bySchemeOnly.values());
109
- }
110
- /** Default token estimate · `len/4` is the industry-standard rough-tokens. */
111
- export function estimateTokens(text) {
112
- return Math.ceil(text.length / 4);
113
- }
114
- //# sourceMappingURL=context-source.js.map