@moda-ai/cli 1.22.1 → 1.22.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.
- package/dist/cli-g8qs2tj9.js +9777 -0
- package/dist/cli-pwbvtes8.js +2312 -0
- package/dist/cli-qszcpda3.js +284 -0
- package/dist/cli.js +4775 -1159
- package/dist/index-6h0nc1r5.js +2765 -0
- package/dist/provision-j5gq8cvx.js +72 -0
- package/package.json +3 -2
- package/skills/integration/index.json +2 -2
- package/dist/agent-schemas.d.ts +0 -3
- package/dist/agent-schemas.js +0 -358
- package/dist/agent.d.ts +0 -5
- package/dist/agent.js +0 -477
- package/dist/analyst-tui.d.ts +0 -81
- package/dist/analyst-tui.js +0 -314
- package/dist/api-client.d.ts +0 -46
- package/dist/api-client.js +0 -120
- package/dist/ask-stream.d.ts +0 -146
- package/dist/ask-stream.js +0 -473
- package/dist/auth-storage.d.ts +0 -35
- package/dist/auth-storage.js +0 -159
- package/dist/auth.d.ts +0 -50
- package/dist/auth.js +0 -694
- package/dist/cli-session.d.ts +0 -26
- package/dist/cli-session.js +0 -169
- package/dist/cli.d.ts +0 -44
- package/dist/command-context.d.ts +0 -62
- package/dist/command-context.js +0 -57
- package/dist/command-registry.d.ts +0 -22
- package/dist/command-registry.js +0 -15
- package/dist/config.d.ts +0 -60
- package/dist/config.js +0 -110
- package/dist/doctor.d.ts +0 -79
- package/dist/doctor.js +0 -1182
- package/dist/errors.d.ts +0 -50
- package/dist/errors.js +0 -64
- package/dist/exit-funnel.d.ts +0 -14
- package/dist/exit-funnel.js +0 -53
- package/dist/harness-artifact-bodies.d.ts +0 -28
- package/dist/harness-artifact-bodies.js +0 -276
- package/dist/harness-candidates.d.ts +0 -77
- package/dist/harness-candidates.js +0 -482
- package/dist/harness-remote.d.ts +0 -135
- package/dist/harness-remote.js +0 -540
- package/dist/harness-report.d.ts +0 -145
- package/dist/harness-report.js +0 -1579
- package/dist/harness-sync-limits.d.ts +0 -8
- package/dist/harness-sync-limits.js +0 -21
- package/dist/harness.d.ts +0 -463
- package/dist/harness.js +0 -4009
- package/dist/init/index.d.ts +0 -78
- package/dist/init/index.js +0 -1442
- package/dist/init/mcp.d.ts +0 -2
- package/dist/init/mcp.js +0 -27
- package/dist/init/progress-board.d.ts +0 -80
- package/dist/init/progress-board.js +0 -225
- package/dist/init/rules.d.ts +0 -6
- package/dist/init/rules.js +0 -171
- package/dist/init/sdk-catalog.d.ts +0 -10
- package/dist/init/sdk-catalog.js +0 -80
- package/dist/init/sdk-guide.d.ts +0 -19
- package/dist/init/sdk-guide.js +0 -185
- package/dist/init/sdk-integration.d.ts +0 -53
- package/dist/init/sdk-integration.js +0 -835
- package/dist/init/sdk-protocol.d.ts +0 -135
- package/dist/init/sdk-protocol.js +0 -152
- package/dist/init/sdk-verification.d.ts +0 -27
- package/dist/init/sdk-verification.js +0 -123
- package/dist/init/tenant.d.ts +0 -67
- package/dist/init/tenant.js +0 -421
- package/dist/intelligence-schema.d.ts +0 -349
- package/dist/intelligence-schema.js +0 -174
- package/dist/keys.d.ts +0 -47
- package/dist/keys.js +0 -136
- package/dist/manifest.d.ts +0 -5
- package/dist/manifest.js +0 -114
- package/dist/markdown-terminal.d.ts +0 -49
- package/dist/markdown-terminal.js +0 -212
- package/dist/output.d.ts +0 -191
- package/dist/output.js +0 -624
- package/dist/production-intelligence.d.ts +0 -175
- package/dist/production-intelligence.js +0 -1430
- package/dist/profile-commands.d.ts +0 -10
- package/dist/profile-commands.js +0 -342
- package/dist/profiles.d.ts +0 -112
- package/dist/profiles.js +0 -573
- package/dist/prompts-ab.d.ts +0 -71
- package/dist/prompts-ab.js +0 -313
- package/dist/prompts-propose.d.ts +0 -15
- package/dist/prompts-propose.js +0 -212
- package/dist/prompts.d.ts +0 -58
- package/dist/prompts.js +0 -418
- package/dist/provision.d.ts +0 -28
- package/dist/provision.js +0 -104
- package/dist/redact.d.ts +0 -2
- package/dist/redact.js +0 -90
- package/dist/schemas.d.ts +0 -225
- package/dist/schemas.js +0 -126
- package/dist/skills-bundled.d.ts +0 -35
- package/dist/skills-bundled.js +0 -157
- package/dist/skills-manifest.d.ts +0 -33
- package/dist/skills-manifest.js +0 -244
- package/dist/skills.d.ts +0 -57
- package/dist/skills.js +0 -783
- package/dist/sync.d.ts +0 -5
- package/dist/sync.js +0 -36
- package/dist/telemetry.d.ts +0 -56
- package/dist/telemetry.js +0 -167
- package/dist/transforms.d.ts +0 -25
- package/dist/transforms.js +0 -102
- package/dist/tui.d.ts +0 -48
- package/dist/tui.js +0 -393
- package/dist/update-check.d.ts +0 -31
- package/dist/update-check.js +0 -141
- package/dist/workspace.d.ts +0 -12
- 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
|
package/dist/exit-funnel.d.ts
DELETED
|
@@ -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
|
package/dist/exit-funnel.js
DELETED
|
@@ -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
|