@moda-ai/cli 1.22.1 → 1.23.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 (118) hide show
  1. package/dist/cli-c4se7a5d.js +762 -0
  2. package/dist/cli-mnqa4rp6.js +1563 -0
  3. package/dist/cli-stmjdgvy.js +2842 -0
  4. package/dist/cli-y55bgg14.js +6980 -0
  5. package/dist/cli-ysegdrm5.js +286 -0
  6. package/dist/cli.js +4779 -1159
  7. package/dist/harness-github-actions-7t3cncnt.js +95 -0
  8. package/dist/index-4ser01zr.js +2769 -0
  9. package/dist/provision-hm1ppwj6.js +74 -0
  10. package/package.json +3 -2
  11. package/skills/integration/index.json +2 -2
  12. package/dist/agent-schemas.d.ts +0 -3
  13. package/dist/agent-schemas.js +0 -358
  14. package/dist/agent.d.ts +0 -5
  15. package/dist/agent.js +0 -477
  16. package/dist/analyst-tui.d.ts +0 -81
  17. package/dist/analyst-tui.js +0 -314
  18. package/dist/api-client.d.ts +0 -46
  19. package/dist/api-client.js +0 -120
  20. package/dist/ask-stream.d.ts +0 -146
  21. package/dist/ask-stream.js +0 -473
  22. package/dist/auth-storage.d.ts +0 -35
  23. package/dist/auth-storage.js +0 -159
  24. package/dist/auth.d.ts +0 -50
  25. package/dist/auth.js +0 -694
  26. package/dist/cli-session.d.ts +0 -26
  27. package/dist/cli-session.js +0 -169
  28. package/dist/cli.d.ts +0 -44
  29. package/dist/command-context.d.ts +0 -62
  30. package/dist/command-context.js +0 -57
  31. package/dist/command-registry.d.ts +0 -22
  32. package/dist/command-registry.js +0 -15
  33. package/dist/config.d.ts +0 -60
  34. package/dist/config.js +0 -110
  35. package/dist/doctor.d.ts +0 -79
  36. package/dist/doctor.js +0 -1182
  37. package/dist/errors.d.ts +0 -50
  38. package/dist/errors.js +0 -64
  39. package/dist/exit-funnel.d.ts +0 -14
  40. package/dist/exit-funnel.js +0 -53
  41. package/dist/harness-artifact-bodies.d.ts +0 -28
  42. package/dist/harness-artifact-bodies.js +0 -276
  43. package/dist/harness-candidates.d.ts +0 -77
  44. package/dist/harness-candidates.js +0 -482
  45. package/dist/harness-remote.d.ts +0 -135
  46. package/dist/harness-remote.js +0 -540
  47. package/dist/harness-report.d.ts +0 -145
  48. package/dist/harness-report.js +0 -1579
  49. package/dist/harness-sync-limits.d.ts +0 -8
  50. package/dist/harness-sync-limits.js +0 -21
  51. package/dist/harness.d.ts +0 -463
  52. package/dist/harness.js +0 -4009
  53. package/dist/init/index.d.ts +0 -78
  54. package/dist/init/index.js +0 -1442
  55. package/dist/init/mcp.d.ts +0 -2
  56. package/dist/init/mcp.js +0 -27
  57. package/dist/init/progress-board.d.ts +0 -80
  58. package/dist/init/progress-board.js +0 -225
  59. package/dist/init/rules.d.ts +0 -6
  60. package/dist/init/rules.js +0 -171
  61. package/dist/init/sdk-catalog.d.ts +0 -10
  62. package/dist/init/sdk-catalog.js +0 -80
  63. package/dist/init/sdk-guide.d.ts +0 -19
  64. package/dist/init/sdk-guide.js +0 -185
  65. package/dist/init/sdk-integration.d.ts +0 -53
  66. package/dist/init/sdk-integration.js +0 -835
  67. package/dist/init/sdk-protocol.d.ts +0 -135
  68. package/dist/init/sdk-protocol.js +0 -152
  69. package/dist/init/sdk-verification.d.ts +0 -27
  70. package/dist/init/sdk-verification.js +0 -123
  71. package/dist/init/tenant.d.ts +0 -67
  72. package/dist/init/tenant.js +0 -421
  73. package/dist/intelligence-schema.d.ts +0 -349
  74. package/dist/intelligence-schema.js +0 -174
  75. package/dist/keys.d.ts +0 -47
  76. package/dist/keys.js +0 -136
  77. package/dist/manifest.d.ts +0 -5
  78. package/dist/manifest.js +0 -114
  79. package/dist/markdown-terminal.d.ts +0 -49
  80. package/dist/markdown-terminal.js +0 -212
  81. package/dist/output.d.ts +0 -191
  82. package/dist/output.js +0 -624
  83. package/dist/production-intelligence.d.ts +0 -175
  84. package/dist/production-intelligence.js +0 -1430
  85. package/dist/profile-commands.d.ts +0 -10
  86. package/dist/profile-commands.js +0 -342
  87. package/dist/profiles.d.ts +0 -112
  88. package/dist/profiles.js +0 -573
  89. package/dist/prompts-ab.d.ts +0 -71
  90. package/dist/prompts-ab.js +0 -313
  91. package/dist/prompts-propose.d.ts +0 -15
  92. package/dist/prompts-propose.js +0 -212
  93. package/dist/prompts.d.ts +0 -58
  94. package/dist/prompts.js +0 -418
  95. package/dist/provision.d.ts +0 -28
  96. package/dist/provision.js +0 -104
  97. package/dist/redact.d.ts +0 -2
  98. package/dist/redact.js +0 -90
  99. package/dist/schemas.d.ts +0 -225
  100. package/dist/schemas.js +0 -126
  101. package/dist/skills-bundled.d.ts +0 -35
  102. package/dist/skills-bundled.js +0 -157
  103. package/dist/skills-manifest.d.ts +0 -33
  104. package/dist/skills-manifest.js +0 -244
  105. package/dist/skills.d.ts +0 -57
  106. package/dist/skills.js +0 -783
  107. package/dist/sync.d.ts +0 -5
  108. package/dist/sync.js +0 -36
  109. package/dist/telemetry.d.ts +0 -56
  110. package/dist/telemetry.js +0 -167
  111. package/dist/transforms.d.ts +0 -25
  112. package/dist/transforms.js +0 -102
  113. package/dist/tui.d.ts +0 -48
  114. package/dist/tui.js +0 -393
  115. package/dist/update-check.d.ts +0 -31
  116. package/dist/update-check.js +0 -141
  117. package/dist/workspace.d.ts +0 -12
  118. package/dist/workspace.js +0 -1294
package/dist/errors.d.ts DELETED
@@ -1,50 +0,0 @@
1
- export declare class CliInputError extends Error {
2
- readonly usage?: string | undefined;
3
- constructor(message: string, usage?: string | undefined);
4
- }
5
- /**
6
- * The authenticated user cannot perform the requested action with their
7
- * current access (no organizations, or insufficient role). This is an access
8
- * problem, not missing auth — the dispatcher maps it to EXIT_ERROR (1),
9
- * never EXIT_AUTH_REQUIRED.
10
- */
11
- export declare class CliAccessError extends Error {
12
- /** Optional follow-up line telling the user how to obtain access. */
13
- readonly guidance?: string;
14
- constructor(message: string, options?: {
15
- guidance?: string;
16
- });
17
- }
18
- export interface AgentInputChoice {
19
- value: string;
20
- label: string;
21
- resume_flags: string[];
22
- }
23
- export interface AgentInputRequest {
24
- id: string;
25
- question: string;
26
- kind: 'select' | 'confirm' | 'text';
27
- choices?: AgentInputChoice[];
28
- resume_command?: string[];
29
- }
30
- export interface AgentAuthRequest {
31
- reason: 'no_stored_credentials' | 'token_expired' | 'token_revoked' | 'renew_failed';
32
- login_command: string[];
33
- resume_command?: string[];
34
- }
35
- export declare class CliInputRequiredError extends Error {
36
- readonly input_request: AgentInputRequest;
37
- constructor(message: string, input_request: AgentInputRequest, usage?: string);
38
- readonly usage?: string;
39
- }
40
- export declare class CliAuthError extends Error {
41
- readonly auth: AgentAuthRequest;
42
- constructor(message: string, auth: AgentAuthRequest, usage?: string);
43
- readonly usage?: string;
44
- }
45
- export declare class CliCancellationError extends Error {
46
- constructor(message: string);
47
- }
48
- export declare function isNetworkLikeError(error: unknown): boolean;
49
- export declare function isApiLikeError(error: unknown): boolean;
50
- //# sourceMappingURL=errors.d.ts.map
package/dist/errors.js DELETED
@@ -1,64 +0,0 @@
1
- export class CliInputError extends Error {
2
- usage;
3
- constructor(message, usage) {
4
- super(message);
5
- this.usage = usage;
6
- this.name = 'CliInputError';
7
- }
8
- }
9
- /**
10
- * The authenticated user cannot perform the requested action with their
11
- * current access (no organizations, or insufficient role). This is an access
12
- * problem, not missing auth — the dispatcher maps it to EXIT_ERROR (1),
13
- * never EXIT_AUTH_REQUIRED.
14
- */
15
- export class CliAccessError extends Error {
16
- /** Optional follow-up line telling the user how to obtain access. */
17
- guidance;
18
- constructor(message, options = {}) {
19
- super(message);
20
- this.name = 'CliAccessError';
21
- this.guidance = options.guidance;
22
- }
23
- }
24
- export class CliInputRequiredError extends Error {
25
- input_request;
26
- constructor(message, input_request, usage) {
27
- super(message);
28
- this.input_request = input_request;
29
- this.name = 'CliInputRequiredError';
30
- if (usage)
31
- this.usage = usage;
32
- }
33
- usage;
34
- }
35
- export class CliAuthError extends Error {
36
- auth;
37
- constructor(message, auth, usage) {
38
- super(message);
39
- this.auth = auth;
40
- this.name = 'CliAuthError';
41
- if (usage)
42
- this.usage = usage;
43
- }
44
- usage;
45
- }
46
- export class CliCancellationError extends Error {
47
- constructor(message) {
48
- super(message);
49
- this.name = 'CliCancellationError';
50
- }
51
- }
52
- export function isNetworkLikeError(error) {
53
- return error instanceof Error &&
54
- (error.message.startsWith('Network error:') ||
55
- error.message.startsWith('Request timed out') ||
56
- error.name === 'AbortError' ||
57
- /(?:ECONNRESET|ECONNREFUSED|ETIMEDOUT|ENOTFOUND|socket hang up)/i.test(error.message));
58
- }
59
- export function isApiLikeError(error) {
60
- return error instanceof Error &&
61
- (error.name === 'ApiError' ||
62
- ('statusCode' in error && typeof error.statusCode === 'number'));
63
- }
64
- //# sourceMappingURL=errors.js.map
@@ -1,14 +0,0 @@
1
- import type { CommandContext } from './command-context.js';
2
- export type RunOutcome = {
3
- kind: 'completed';
4
- exitCode?: number;
5
- } | {
6
- kind: 'error';
7
- error: unknown;
8
- } | {
9
- kind: 'cancelled';
10
- reason?: string;
11
- };
12
- export declare function finishRun(context: CommandContext, outcome: RunOutcome): Promise<number>;
13
- export declare function exitCodeForError(error: unknown): number;
14
- //# sourceMappingURL=exit-funnel.d.ts.map
@@ -1,53 +0,0 @@
1
- import { CliAccessError, CliAuthError, CliCancellationError, CliInputRequiredError, } from './errors.js';
2
- import { EXIT_AUTH_REQUIRED, EXIT_CANCELLED, EXIT_ERROR, EXIT_INPUT_REQUIRED, EXIT_OK, wasOutputCancelled, } from './output.js';
3
- const finishedContexts = new WeakSet();
4
- export async function finishRun(context, outcome) {
5
- if (finishedContexts.has(context)) {
6
- return exitCodeForOutcome(outcome);
7
- }
8
- finishedContexts.add(context);
9
- if (outcome.kind === 'completed') {
10
- // Guarantee the stream contract structurally: exactly one terminal event
11
- // ends every stream. A handler that returned without writing one gets a
12
- // synthetic `completed` (a no-op in non-stream modes and when a terminal
13
- // event was already emitted).
14
- if (!context.output.hasTerminalOutput()) {
15
- context.output.writeEvent({ event: 'completed', status: 'ok' });
16
- }
17
- context.output.close();
18
- if (wasOutputCancelled())
19
- return EXIT_CANCELLED;
20
- return outcome.exitCode ?? EXIT_OK;
21
- }
22
- if (outcome.kind === 'cancelled') {
23
- context.output.writeCancelled(outcome.reason ?? 'Command cancelled.');
24
- context.output.close();
25
- return EXIT_CANCELLED;
26
- }
27
- context.output.writeError(outcome.error);
28
- context.output.close();
29
- if (wasOutputCancelled())
30
- return EXIT_CANCELLED;
31
- return exitCodeForError(outcome.error);
32
- }
33
- export function exitCodeForError(error) {
34
- if (error instanceof CliAuthError)
35
- return EXIT_AUTH_REQUIRED;
36
- if (error instanceof CliInputRequiredError)
37
- return EXIT_INPUT_REQUIRED;
38
- if (error instanceof CliCancellationError)
39
- return EXIT_CANCELLED;
40
- // An access problem (missing org / insufficient role) is a hard failure,
41
- // never auth-required — it stays EXIT_ERROR (the default), made explicit here.
42
- if (error instanceof CliAccessError)
43
- return EXIT_ERROR;
44
- return EXIT_ERROR;
45
- }
46
- function exitCodeForOutcome(outcome) {
47
- if (outcome.kind === 'completed')
48
- return outcome.exitCode ?? EXIT_OK;
49
- if (outcome.kind === 'cancelled')
50
- return EXIT_CANCELLED;
51
- return exitCodeForError(outcome.error);
52
- }
53
- //# sourceMappingURL=exit-funnel.js.map
@@ -1,28 +0,0 @@
1
- import type { HarnessArtifactType, HarnessEvidence, HarnessGraph } from './harness.js';
2
- export interface ArtifactBodyAnnotation {
3
- values: Record<string, string[]>;
4
- evidence: HarnessEvidence;
5
- sourceAbsPath: string;
6
- rootDir: string;
7
- }
8
- export interface ArtifactBodyFileItem {
9
- path: string;
10
- rootDir: string;
11
- text?: string;
12
- }
13
- export declare function extractToolBodyFromText(text: string, toolName: string): string | undefined;
14
- export declare function annotatedSourceCandidates(sourcePath: string, annotation: ArtifactBodyAnnotation): string[];
15
- /**
16
- * Resolve an annotated artifact `path=` to a repo-relative sourcePath.
17
- * Prefer the nested annotation-relative file when present so later whole-file
18
- * hydration and content hashing look in the right place.
19
- */
20
- export declare function resolveAnnotatedSourcePath(sourcePath: string, annotation: ArtifactBodyAnnotation): string;
21
- export declare function readPromptOrToolBody(type: HarnessArtifactType, sourcePath: string, annotation: ArtifactBodyAnnotation): string | undefined;
22
- export declare function extractToolBody(file: ArtifactBodyFileItem, tool: {
23
- name: string;
24
- reason: string;
25
- }): string | undefined;
26
- export declare function hydratePromptAndToolBodies(graph: HarnessGraph, rootDir: string): void;
27
- export declare function enrichScanCaptureMetadata(graph: HarnessGraph): void;
28
- //# sourceMappingURL=harness-artifact-bodies.d.ts.map
@@ -1,276 +0,0 @@
1
- import { existsSync, lstatSync, readFileSync, realpathSync } from 'node:fs';
2
- import { basename, dirname, isAbsolute, relative, resolve } from 'node:path';
3
- import { artifactHasBody, firstEvidencePath, requiresArtifactBody, } from './harness-report.js';
4
- const MAX_SCANNED_FILE_BYTES = 512 * 1024;
5
- const SNAPSHOT_BUDGET_SKIP_REASONS = new Set([
6
- 'file too large for harness scan',
7
- 'scan text budget exceeded',
8
- ]);
9
- function first(values) {
10
- return values?.[0];
11
- }
12
- function normalizePath(path) {
13
- return path.replace(/\\/g, '/');
14
- }
15
- function isTextFile(path) {
16
- return /\.(ts|tsx|js|jsx|mjs|cjs|py|md|json|ya?ml|toml|txt|sh|rb|go|rs|swift|kt|java|cs|php|sql|graphql|gql|proto|css|scss|less|html|vue|svelte)$/i.test(path);
17
- }
18
- function isSecretishPath(path) {
19
- const normalized = normalizePath(path).toLowerCase();
20
- return normalized.includes('.env') ||
21
- normalized.endsWith('.pem') ||
22
- normalized.endsWith('.key') ||
23
- normalized.includes('/secrets/') ||
24
- normalized.includes('/credentials/');
25
- }
26
- function isOutsideRoot(rel) {
27
- return rel === '' || rel === '..' || rel.startsWith('../') || isAbsolute(rel);
28
- }
29
- /** Walk from `fromAbs` up to `rootAbs` (inclusive), bounded by `maxDepth`. */
30
- function walkAncestorDirs(fromAbs, rootAbs, maxDepth = 8) {
31
- const dirs = [];
32
- let cursor = fromAbs;
33
- for (let depth = 0; depth < maxDepth; depth += 1) {
34
- dirs.push(cursor);
35
- if (normalizePath(cursor) === normalizePath(rootAbs))
36
- break;
37
- const parent = dirname(cursor);
38
- if (parent === cursor)
39
- break;
40
- cursor = parent;
41
- }
42
- return dirs;
43
- }
44
- function readBoundedRepoText(absPath, rootDir) {
45
- if (!existsSync(absPath))
46
- return undefined;
47
- const rel = normalizePath(relative(rootDir, absPath));
48
- // Containment: never read files that resolve outside the scan root. Guards against
49
- // `..` traversal or absolute paths (e.g. via a crafted `path=` annotation / sourcePath /
50
- // evidence path) being hydrated into synced artifact bodies.
51
- if (isOutsideRoot(rel))
52
- return undefined;
53
- if (isSecretishPath(rel))
54
- return undefined;
55
- let stat;
56
- try {
57
- stat = lstatSync(absPath);
58
- }
59
- catch {
60
- return undefined;
61
- }
62
- if (!stat.isFile() || stat.isSymbolicLink() || stat.size > MAX_SCANNED_FILE_BYTES) {
63
- return undefined;
64
- }
65
- if (!isTextFile(rel))
66
- return undefined;
67
- // Realpath containment: the lexical `rel` check is purely textual and `lstatSync` only
68
- // inspects the final component, so an intermediate directory symlink (e.g.
69
- // `tools/outside -> /tmp/public`) could still let `readFileSync` follow the link out of
70
- // the repo. Canonicalize both sides and re-check before reading.
71
- try {
72
- if (isOutsideRoot(normalizePath(relative(realpathSync(rootDir), realpathSync(absPath))))) {
73
- return undefined;
74
- }
75
- }
76
- catch {
77
- return undefined;
78
- }
79
- try {
80
- return readFileSync(absPath, 'utf8');
81
- }
82
- catch {
83
- return undefined;
84
- }
85
- }
86
- function readRepoRelativeText(rootDir, relPath) {
87
- return readBoundedRepoText(resolve(rootDir, relPath), rootDir);
88
- }
89
- function isDedicatedToolFilePath(path) {
90
- return /(^|\/)tools?\//i.test(path) && /\.(ts|tsx|js|mjs|py)$/i.test(path);
91
- }
92
- function firstEvidenceLineNumber(evidence) {
93
- if (!Array.isArray(evidence))
94
- return undefined;
95
- for (const entry of evidence) {
96
- if (typeof entry.line === 'number' && Number.isFinite(entry.line) && entry.line > 0) {
97
- return Math.floor(entry.line);
98
- }
99
- }
100
- return undefined;
101
- }
102
- function extractSnippetAroundLine(text, line, radius) {
103
- const lines = text.split(/\r?\n/);
104
- const start = Math.max(0, line - 1 - radius);
105
- // `line` is 1-indexed and slice's end is exclusive, so include through
106
- // (line-1)+radius: end = line + radius. Using line-1+radius dropped the tail line.
107
- const end = Math.min(lines.length, line + radius);
108
- return lines.slice(start, end).join('\n');
109
- }
110
- export function extractToolBodyFromText(text, toolName) {
111
- const lines = text.split(/\r?\n/);
112
- const normalizedName = toolName.replace(/\s+/g, '').toLowerCase();
113
- for (let index = 0; index < lines.length; index += 1) {
114
- const line = lines[index];
115
- const compact = line.replace(/\s+/g, '').toLowerCase();
116
- const mentionsTool = compact.includes(normalizedName) || compact.includes(`def${normalizedName}`);
117
- if (!mentionsTool)
118
- continue;
119
- if (line.includes('function_tool') ||
120
- line.includes('tool(') ||
121
- line.includes('createTool') ||
122
- line.includes('functionTool') ||
123
- line.includes('dynamicTool') ||
124
- line.includes('new Tool') ||
125
- line.includes('def ')) {
126
- return extractSnippetAroundLine(text, index + 1, 30);
127
- }
128
- }
129
- return undefined;
130
- }
131
- export function annotatedSourceCandidates(sourcePath, annotation) {
132
- const explicitSourcePath = normalizePath(sourcePath) !== normalizePath(annotation.evidence.path);
133
- // Relative `path=` annotations in nested packages usually mean "relative to the
134
- // package/harness root that owns the annotated file", not the file's own directory.
135
- // Example: harnesses/boardy-ops/src/ops.ts with path=prompts/ops-system.prompt.md
136
- // resolves to harnesses/boardy-ops/prompts/ops-system.prompt.md (one level up from
137
- // src/), not harnesses/boardy-ops/src/prompts/... Walk parents between the
138
- // annotated file and the scan root, then fall back to the scan-root candidate
139
- // for flat repos that annotate from the repository root.
140
- if (!explicitSourcePath) {
141
- return [
142
- resolve(annotation.rootDir, sourcePath),
143
- resolve(dirname(annotation.sourceAbsPath), sourcePath),
144
- annotation.sourceAbsPath,
145
- ];
146
- }
147
- const rootAbs = resolve(annotation.rootDir);
148
- const candidates = walkAncestorDirs(dirname(annotation.sourceAbsPath), rootAbs)
149
- .map((dir) => resolve(dir, sourcePath));
150
- candidates.push(resolve(annotation.rootDir, sourcePath));
151
- return candidates;
152
- }
153
- function readAnnotatedSourceText(sourcePath, annotation) {
154
- const seen = new Set();
155
- for (const candidate of annotatedSourceCandidates(sourcePath, annotation)) {
156
- if (seen.has(candidate))
157
- continue;
158
- seen.add(candidate);
159
- const text = readBoundedRepoText(candidate, annotation.rootDir);
160
- if (text !== undefined)
161
- return text;
162
- }
163
- return undefined;
164
- }
165
- /**
166
- * Resolve an annotated artifact `path=` to a repo-relative sourcePath.
167
- * Prefer the nested annotation-relative file when present so later whole-file
168
- * hydration and content hashing look in the right place.
169
- */
170
- export function resolveAnnotatedSourcePath(sourcePath, annotation) {
171
- const seen = new Set();
172
- for (const candidate of annotatedSourceCandidates(sourcePath, annotation)) {
173
- if (seen.has(candidate))
174
- continue;
175
- seen.add(candidate);
176
- if (readBoundedRepoText(candidate, annotation.rootDir) === undefined)
177
- continue;
178
- const rel = normalizePath(relative(annotation.rootDir, candidate));
179
- if (!isOutsideRoot(rel))
180
- return rel;
181
- }
182
- return sourcePath;
183
- }
184
- export function readPromptOrToolBody(type, sourcePath, annotation) {
185
- if (!requiresArtifactBody(type))
186
- return undefined;
187
- const text = readAnnotatedSourceText(sourcePath, annotation);
188
- if (text === undefined)
189
- return undefined;
190
- if (type === 'prompt' || isDedicatedToolFilePath(sourcePath))
191
- return text;
192
- return extractToolBodyFromText(text, first(annotation.values.name) ?? basename(sourcePath)) ?? text;
193
- }
194
- export function extractToolBody(file, tool) {
195
- const text = file.text ?? readRepoRelativeText(file.rootDir, file.path);
196
- if (!text)
197
- return undefined;
198
- if (isDedicatedToolFilePath(file.path))
199
- return text;
200
- return extractToolBodyFromText(text, tool.name);
201
- }
202
- function hydrateArtifactBody(artifact, rootDir) {
203
- if (artifact.sourcePath) {
204
- const wholeFile = readRepoRelativeText(rootDir, artifact.sourcePath)
205
- ?? readSourcePathRelativeToEvidence(artifact, rootDir);
206
- if (wholeFile !== undefined) {
207
- if (artifact.type === 'prompt' || isDedicatedToolFilePath(artifact.sourcePath)) {
208
- return wholeFile;
209
- }
210
- if (artifact.type === 'tool') {
211
- return extractToolBodyFromText(wholeFile, artifact.name) ?? wholeFile;
212
- }
213
- }
214
- }
215
- const evidenceLine = firstEvidenceLineNumber(artifact.evidence);
216
- const evidencePath = firstEvidencePath(artifact);
217
- if (evidencePath) {
218
- const text = readRepoRelativeText(rootDir, evidencePath);
219
- if (text !== undefined) {
220
- if (evidenceLine !== undefined) {
221
- return extractSnippetAroundLine(text, evidenceLine, artifact.type === 'tool' ? 30 : 80);
222
- }
223
- // No line number (common for agent/hosted analyst citations): fall back to
224
- // the whole evidence file so the body is still captured in full.
225
- if (artifact.type === 'prompt' || isDedicatedToolFilePath(evidencePath))
226
- return text;
227
- if (artifact.type === 'tool')
228
- return extractToolBodyFromText(text, artifact.name) ?? text;
229
- }
230
- }
231
- return undefined;
232
- }
233
- /**
234
- * Fallback for nested relative `sourcePath` values that were recorded before the
235
- * annotation-relative resolver existed: walk parent dirs beside the evidence file
236
- * looking for the package-local prompt/tool path.
237
- */
238
- function readSourcePathRelativeToEvidence(artifact, rootDir) {
239
- if (!artifact.sourcePath)
240
- return undefined;
241
- const evidencePath = firstEvidencePath(artifact);
242
- if (!evidencePath)
243
- return undefined;
244
- const rootAbs = resolve(rootDir);
245
- for (const dir of walkAncestorDirs(dirname(resolve(rootDir, evidencePath)), rootAbs)) {
246
- const text = readBoundedRepoText(resolve(dir, artifact.sourcePath), rootDir);
247
- if (text !== undefined)
248
- return text;
249
- }
250
- return undefined;
251
- }
252
- export function hydratePromptAndToolBodies(graph, rootDir) {
253
- for (const artifact of graph.artifacts) {
254
- if (!requiresArtifactBody(artifact.type) || artifactHasBody(artifact)) {
255
- continue;
256
- }
257
- const hydrated = hydrateArtifactBody(artifact, rootDir);
258
- if (hydrated)
259
- artifact.body = hydrated;
260
- }
261
- }
262
- function countIncompletePromptToolBodies(graph) {
263
- return graph.artifacts.filter((artifact) => requiresArtifactBody(artifact.type) &&
264
- !artifactHasBody(artifact)).length;
265
- }
266
- export function enrichScanCaptureMetadata(graph) {
267
- const budgetSkips = graph.scan.skipped
268
- .map((skip) => skip.reason)
269
- .filter((reason) => SNAPSHOT_BUDGET_SKIP_REASONS.has(reason));
270
- const truncationReasons = [...new Set(budgetSkips)];
271
- graph.scan.truncated = truncationReasons.length > 0;
272
- graph.scan.truncationReasons = truncationReasons.length > 0 ? truncationReasons : undefined;
273
- const incomplete = countIncompletePromptToolBodies(graph);
274
- graph.scan.incompleteArtifactCount = incomplete > 0 ? incomplete : undefined;
275
- }
276
- //# sourceMappingURL=harness-artifact-bodies.js.map
@@ -1,77 +0,0 @@
1
- /**
2
- * Static harness-candidate pre-pass (Phase 2 prototype).
3
- *
4
- * A deterministic, LLM-free detector that walks a repo and extracts
5
- * *candidate* harness surfaces — LLM SDK call sites, agent-framework
6
- * constructs, tool definitions/schemas, prompt files and prompt-like string
7
- * literals, and model config assignments — each with a file:line citation,
8
- * a confidence heuristic, and a short excerpt.
9
- *
10
- * Purpose: today `harness analyze` (local or hosted) has a single agent read
11
- * everything, which caps out around ~450 files / ~20 minutes. This pre-pass is
12
- * pure regex/heuristics (no LLM calls, no network), runs in milliseconds per
13
- * thousand files, and produces a prioritized read plan so the LLM
14
- * classification stage only needs to open the files that plausibly matter.
15
- *
16
- * Output contract: `harness_candidates.v0.1` — see {@link HarnessCandidateReport}.
17
- * Candidates are recall-oriented: false positives are expected and cheap (the
18
- * LLM stage filters them); false negatives are the expensive failure mode.
19
- */
20
- import { type FileInventoryItem, type HarnessScanMetadata } from './harness.js';
21
- export type HarnessCandidateKind = 'llm_call_site' | 'framework_construct' | 'tool_definition' | 'tool_schema' | 'message_construction' | 'prompt_file' | 'prompt_literal' | 'model_config';
22
- export type HarnessCandidateConfidence = 'high' | 'medium' | 'low';
23
- export interface HarnessCandidate {
24
- kind: HarnessCandidateKind;
25
- /** Repo-relative, forward-slash path. */
26
- path: string;
27
- /** 1-based inclusive line range. */
28
- startLine: number;
29
- endLine: number;
30
- confidence: HarnessCandidateConfidence;
31
- /** Which detector fired, e.g. `openai.chat_completions_create`. */
32
- signal: string;
33
- /** Normalized single-line excerpt, capped at {@link EXCERPT_MAX_CHARS}. */
34
- excerpt: string;
35
- }
36
- export interface HarnessCandidateFileSummary {
37
- path: string;
38
- /** Sum of per-candidate confidence weights; higher = read first. */
39
- score: number;
40
- kinds: HarnessCandidateKind[];
41
- candidateCount: number;
42
- }
43
- export interface HarnessCandidateStats {
44
- filesScanned: number;
45
- filesWithCandidates: number;
46
- candidateCount: number;
47
- textBytesScanned: number;
48
- durationMs: number;
49
- filesPerSecond: number;
50
- }
51
- export interface HarnessCandidateReport {
52
- schemaVersion: 'harness_candidates.v0.1';
53
- generatedAt: string;
54
- rootDir: string;
55
- stats: HarnessCandidateStats;
56
- candidates: HarnessCandidate[];
57
- /** Per-file rollup sorted by descending score — the LLM-stage read plan. */
58
- files: HarnessCandidateFileSummary[];
59
- skipped: HarnessScanMetadata['skipped'];
60
- }
61
- /**
62
- * Pure detector core: run every heuristic over an already-materialized file
63
- * inventory. No filesystem access — unit tests and the walker wrapper both
64
- * call this.
65
- */
66
- export declare function detectHarnessCandidatesInFiles(files: Array<Pick<FileInventoryItem, 'path' | 'text'>>): {
67
- candidates: HarnessCandidate[];
68
- files: HarnessCandidateFileSummary[];
69
- };
70
- /**
71
- * Walk `rootDir` with the harness inventory walker (same skip/secret/symlink
72
- * budget rules as `harness scan`, widened to include all prompt-doc files)
73
- * and run the static detector over it.
74
- */
75
- export declare function detectHarnessCandidates(rootDir: string): HarnessCandidateReport;
76
- export declare function writeHarnessCandidatesReport(report: HarnessCandidateReport, outPath: string): string;
77
- //# sourceMappingURL=harness-candidates.d.ts.map