@amrishkhan05/frankly 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (123) hide show
  1. package/.claude-plugin/marketplace.json +27 -0
  2. package/.claude-plugin/plugin.json +11 -0
  3. package/.github/ISSUE_TEMPLATE/bug_report.md +20 -0
  4. package/.github/ISSUE_TEMPLATE/documentation.md +15 -0
  5. package/.github/ISSUE_TEMPLATE/feature_request.md +15 -0
  6. package/.github/PULL_REQUEST_TEMPLATE.md +20 -0
  7. package/.github/copilot-instructions.md +12 -0
  8. package/.github/plugin/marketplace.json +20 -0
  9. package/.github/plugin/plugin.json +16 -0
  10. package/.mcp.json +10 -0
  11. package/LICENSE +21 -0
  12. package/README.md +394 -0
  13. package/commands/frankly-help.toml +2 -0
  14. package/commands/frankly-plan.toml +2 -0
  15. package/commands/frankly-review.toml +2 -0
  16. package/commands/frankly-verify.toml +2 -0
  17. package/commands/frankly.toml +2 -0
  18. package/dist/analysis/change-classifier.d.ts +65 -0
  19. package/dist/analysis/change-classifier.d.ts.map +1 -0
  20. package/dist/analysis/change-classifier.js +241 -0
  21. package/dist/analysis/change-classifier.js.map +1 -0
  22. package/dist/analysis/repository-intelligence.d.ts +15 -0
  23. package/dist/analysis/repository-intelligence.d.ts.map +1 -0
  24. package/dist/analysis/repository-intelligence.js +179 -0
  25. package/dist/analysis/repository-intelligence.js.map +1 -0
  26. package/dist/core/config.d.ts +393 -0
  27. package/dist/core/config.d.ts.map +1 -0
  28. package/dist/core/config.js +130 -0
  29. package/dist/core/config.js.map +1 -0
  30. package/dist/core/engine.d.ts +20 -0
  31. package/dist/core/engine.d.ts.map +1 -0
  32. package/dist/core/engine.js +117 -0
  33. package/dist/core/engine.js.map +1 -0
  34. package/dist/core/errors.d.ts +27 -0
  35. package/dist/core/errors.d.ts.map +1 -0
  36. package/dist/core/errors.js +48 -0
  37. package/dist/core/errors.js.map +1 -0
  38. package/dist/core/index.d.ts +8 -0
  39. package/dist/core/index.d.ts.map +1 -0
  40. package/dist/core/index.js +8 -0
  41. package/dist/core/index.js.map +1 -0
  42. package/dist/core/models.d.ts +155 -0
  43. package/dist/core/models.d.ts.map +1 -0
  44. package/dist/core/models.js +6 -0
  45. package/dist/core/models.js.map +1 -0
  46. package/dist/git/index.d.ts +7 -0
  47. package/dist/git/index.d.ts.map +1 -0
  48. package/dist/git/index.js +6 -0
  49. package/dist/git/index.js.map +1 -0
  50. package/dist/git/repository.d.ts +44 -0
  51. package/dist/git/repository.d.ts.map +1 -0
  52. package/dist/git/repository.js +198 -0
  53. package/dist/git/repository.js.map +1 -0
  54. package/dist/graph/symbols.d.ts +45 -0
  55. package/dist/graph/symbols.d.ts.map +1 -0
  56. package/dist/graph/symbols.js +204 -0
  57. package/dist/graph/symbols.js.map +1 -0
  58. package/dist/integrations/claude/checkpoint.d.ts +2 -0
  59. package/dist/integrations/claude/checkpoint.d.ts.map +1 -0
  60. package/dist/integrations/claude/checkpoint.js +33 -0
  61. package/dist/integrations/claude/checkpoint.js.map +1 -0
  62. package/dist/integrations/cli/demo.d.ts +3 -0
  63. package/dist/integrations/cli/demo.d.ts.map +1 -0
  64. package/dist/integrations/cli/demo.js +31 -0
  65. package/dist/integrations/cli/demo.js.map +1 -0
  66. package/dist/integrations/cli/index.d.ts +7 -0
  67. package/dist/integrations/cli/index.d.ts.map +1 -0
  68. package/dist/integrations/cli/index.js +181 -0
  69. package/dist/integrations/cli/index.js.map +1 -0
  70. package/dist/integrations/mcp/index.d.ts +3 -0
  71. package/dist/integrations/mcp/index.d.ts.map +1 -0
  72. package/dist/integrations/mcp/index.js +7 -0
  73. package/dist/integrations/mcp/index.js.map +1 -0
  74. package/dist/integrations/mcp/server.d.ts +14 -0
  75. package/dist/integrations/mcp/server.d.ts.map +1 -0
  76. package/dist/integrations/mcp/server.js +232 -0
  77. package/dist/integrations/mcp/server.js.map +1 -0
  78. package/dist/project/detector.d.ts +70 -0
  79. package/dist/project/detector.d.ts.map +1 -0
  80. package/dist/project/detector.js +214 -0
  81. package/dist/project/detector.js.map +1 -0
  82. package/dist/report/json.d.ts +26 -0
  83. package/dist/report/json.d.ts.map +1 -0
  84. package/dist/report/json.js +148 -0
  85. package/dist/report/json.js.map +1 -0
  86. package/dist/review/red-ink-review.d.ts +26 -0
  87. package/dist/review/red-ink-review.d.ts.map +1 -0
  88. package/dist/review/red-ink-review.js +103 -0
  89. package/dist/review/red-ink-review.js.map +1 -0
  90. package/fixtures/retry-429/README.md +8 -0
  91. package/fixtures/retry-429/baseline/package.json +4 -0
  92. package/fixtures/retry-429/baseline/src/client.test.ts +8 -0
  93. package/fixtures/retry-429/baseline/src/client.ts +3 -0
  94. package/fixtures/retry-429/baseline/src/retry-policy.ts +1 -0
  95. package/fixtures/retry-429/baseline/tsconfig.json +7 -0
  96. package/fixtures/retry-429/candidate/src/client.ts +5 -0
  97. package/fixtures/retry-429/candidate/src/retry-strategy.ts +9 -0
  98. package/fixtures/retry-429/candidate/src/unrelated-format.ts +1 -0
  99. package/hooks/copilot-activate.js +9 -0
  100. package/hooks/copilot-hooks.json +13 -0
  101. package/hooks/hooks.json +13 -0
  102. package/package.json +70 -0
  103. package/scripts/run.cjs +32 -0
  104. package/skills/frankly/SKILL.md +12 -0
  105. package/src/analysis/change-classifier.ts +285 -0
  106. package/src/analysis/repository-intelligence.ts +236 -0
  107. package/src/core/config.ts +149 -0
  108. package/src/core/engine.ts +143 -0
  109. package/src/core/errors.ts +56 -0
  110. package/src/core/index.ts +8 -0
  111. package/src/core/models.ts +280 -0
  112. package/src/git/index.ts +7 -0
  113. package/src/git/repository.ts +207 -0
  114. package/src/graph/symbols.ts +254 -0
  115. package/src/integrations/claude/checkpoint.ts +40 -0
  116. package/src/integrations/cli/demo.ts +34 -0
  117. package/src/integrations/cli/index.ts +211 -0
  118. package/src/integrations/mcp/index.ts +7 -0
  119. package/src/integrations/mcp/server.ts +257 -0
  120. package/src/project/detector.ts +242 -0
  121. package/src/report/json.ts +175 -0
  122. package/src/review/red-ink-review.ts +125 -0
  123. package/src/tests/phase1.test.ts +57 -0
@@ -0,0 +1,149 @@
1
+ /**
2
+ * Configuration types and loader for Frankly
3
+ */
4
+
5
+ import { z } from "zod";
6
+ import * as fs from "node:fs";
7
+ import * as path from "node:path";
8
+
9
+ export type FranklyTrigger = "manual" | "checkpoint" | "continuous" | "ci" | "off";
10
+ export type FranklyAction = "observe" | "advise" | "correct" | "enforce";
11
+ export type FranklyIntensity = "lite" | "full" | "ultra" | "off";
12
+ export type FranklyPersonality = "conservative" | "senior" | "witty";
13
+
14
+ const ConfigSchema = z.object({
15
+ trigger: z.enum(["manual", "checkpoint", "continuous", "ci", "off"]).default("checkpoint"),
16
+ action: z.enum(["observe", "advise", "correct", "enforce"]).default("correct"),
17
+ intensity: z.enum(["lite", "full", "ultra", "off"]).default("full"),
18
+ personality: z.enum(["conservative", "senior", "witty"]).default("senior"),
19
+ maxCorrectionPasses: z.number().int().min(0).max(5).default(1),
20
+
21
+ analysis: z
22
+ .object({
23
+ minimumSufficientChange: z.boolean().default(true),
24
+ taskAlignment: z.boolean().default(true),
25
+ reuseFirst: z.boolean().default(true),
26
+ scopeDrift: z.boolean().default(true),
27
+ refactorContamination: z.boolean().default(true),
28
+ unnecessaryAbstractions: z.boolean().default(true),
29
+ unnecessaryDependencies: z.boolean().default(true),
30
+ duplication: z.boolean().default(true),
31
+ blastRadius: z.boolean().default(true),
32
+ changeSurface: z.boolean().default(true),
33
+ changeNecessity: z.boolean().default(true),
34
+ reviewBurden: z.boolean().default(true),
35
+ contractChanges: z.boolean().default(true),
36
+ behavioralChanges: z.boolean().default(true),
37
+ testImpact: z.boolean().default(true),
38
+ missingRegressionTests: z.boolean().default(true),
39
+ })
40
+ .default({}),
41
+
42
+ restraint: z
43
+ .object({
44
+ enabled: z.boolean().default(true),
45
+ yagni: z.boolean().default(true),
46
+ reuseExistingCodeFirst: z.boolean().default(true),
47
+ stdlibFirst: z.boolean().default(true),
48
+ platformNativeFirst: z.boolean().default(true),
49
+ installedDependenciesFirst: z.boolean().default(true),
50
+ avoidNewDependencies: z.boolean().default(true),
51
+ avoidPrematureAbstraction: z.boolean().default(true),
52
+ avoidUnnecessaryGeneralization: z.boolean().default(true),
53
+ avoidUnrelatedRefactors: z.boolean().default(true),
54
+ preferLocalChange: z.boolean().default(true),
55
+ preferSmallestSafeDiff: z.boolean().default(true),
56
+ preserveSafetyGuardrails: z.boolean().default(true),
57
+ })
58
+ .default({}),
59
+
60
+ tests: z
61
+ .object({
62
+ predictAffected: z.boolean().default(true),
63
+ distinguishPredictedAndExecuted: z.boolean().default(true),
64
+ detectMissingRegressionCoverage: z.boolean().default(true),
65
+ riskBasedExecution: z.boolean().default(true),
66
+ adapters: z
67
+ .object({
68
+ jest: z.boolean().default(true),
69
+ vitest: z.boolean().default(true),
70
+ nx: z.boolean().default(true),
71
+ })
72
+ .default({}),
73
+ })
74
+ .default({}),
75
+
76
+ correction: z
77
+ .object({
78
+ enabled: z.boolean().default(true),
79
+ preserveRequestedBehavior: z.boolean().default(true),
80
+ preserveSecurity: z.boolean().default(true),
81
+ preserveValidation: z.boolean().default(true),
82
+ preserveAccessibility: z.boolean().default(true),
83
+ preserveDataIntegrity: z.boolean().default(true),
84
+ preserveErrorHandling: z.boolean().default(true),
85
+ preserveRegressionCoverage: z.boolean().default(true),
86
+ })
87
+ .default({}),
88
+
89
+ report: z
90
+ .object({
91
+ redInkReview: z.boolean().default(true),
92
+ showEvidence: z.boolean().default(true),
93
+ showConfidence: z.boolean().default(true),
94
+ showLowConfidenceFindings: z.boolean().default(false),
95
+ showPersonalityLine: z.boolean().default(true),
96
+ progressiveDisclosure: z.boolean().default(true),
97
+ })
98
+ .default({}),
99
+
100
+ privacy: z
101
+ .object({
102
+ localOnly: z.boolean().default(true),
103
+ telemetry: z.boolean().default(false),
104
+ externalLLMCalls: z.boolean().default(false),
105
+ embeddings: z.boolean().default(false),
106
+ remoteCodeUpload: z.boolean().default(false),
107
+ })
108
+ .default({}),
109
+ });
110
+
111
+ export type FranklyConfig = z.infer<typeof ConfigSchema>;
112
+
113
+ export function loadConfig(raw: unknown): FranklyConfig {
114
+ return ConfigSchema.parse(raw);
115
+ }
116
+
117
+ export function getDefaultConfig(): FranklyConfig {
118
+ return ConfigSchema.parse({});
119
+ }
120
+
121
+ export function loadRepositoryConfig(root: string): FranklyConfig {
122
+ const configPath = path.join(root, "frankly.config.json");
123
+ if (!fs.existsSync(configPath)) return getDefaultConfig();
124
+ const raw = JSON.parse(fs.readFileSync(configPath, "utf8")) as Record<string, unknown>;
125
+ delete raw.$schema;
126
+ return loadConfig(raw);
127
+ }
128
+
129
+ export const INTENSITY_DESCRIPTIONS = {
130
+ lite: "Only obvious issues: unrelated changes, duplicate code, unnecessary dependency, obvious scope drift",
131
+ full: "Complete analysis: YAGNI, reuse, abstraction, scope, blast radius, tests, contracts, minimization",
132
+ ultra: "Aggressively challenge: premature generalization, new helpers, abstraction, unnecessary defensive complexity, dependency additions, avoidable files/symbols, unrelated refactoring",
133
+ off: "No analysis",
134
+ };
135
+
136
+ export const TRIGGER_DESCRIPTIONS = {
137
+ manual: "Only run when explicitly requested",
138
+ checkpoint: "Run automatically at meaningful task completion points",
139
+ continuous: "Run on meaningful debounced edit batches",
140
+ ci: "Run on committed diff / CI / PR context",
141
+ off: "Disable Frankly",
142
+ };
143
+
144
+ export const ACTION_DESCRIPTIONS = {
145
+ observe: "Analyze only. Never instruct the agent to change the patch.",
146
+ advise: "Return recommendations to the coding agent.",
147
+ correct: "Give the coding agent exactly one automatic correction/simplification opportunity.",
148
+ enforce: "Block completion when configured policies fail.",
149
+ };
@@ -0,0 +1,143 @@
1
+ import * as path from "node:path";
2
+ import type { FranklyConfig } from "./config.js";
3
+ import { loadConfig, loadRepositoryConfig } from "./config.js";
4
+ import type { AnalysisResult, ChangedFile, ChangedSymbol, ExecutedTest, TaskPlan } from "./models.js";
5
+ import { countDiffLines, Repository } from "../git/repository.js";
6
+ import { ProjectDetector } from "../project/detector.js";
7
+ import { SymbolExtractor } from "../graph/symbols.js";
8
+ import { ChangeAnalyzer } from "../analysis/change-classifier.js";
9
+ import { inspectRepository } from "../analysis/repository-intelligence.js";
10
+ import { RedInkReviewBuilder } from "../review/red-ink-review.js";
11
+
12
+ export interface AnalysisEngine {
13
+ analyze(task: string, configOverrides?: Partial<FranklyConfig>, executedTests?: ExecutedTest[]): Promise<AnalysisResult>;
14
+ plan(task: string): Promise<TaskPlan>;
15
+ }
16
+
17
+ export class FranklyAnalysisEngine implements AnalysisEngine {
18
+ constructor(
19
+ private readonly repository: Repository,
20
+ private readonly projectDetector: ProjectDetector,
21
+ private readonly symbolExtractor: SymbolExtractor,
22
+ private readonly config: FranklyConfig,
23
+ ) {}
24
+
25
+ async plan(task: string): Promise<TaskPlan> {
26
+ const analyzer = new ChangeAnalyzer();
27
+ const keywords = analyzer.extractTaskKeywords(task);
28
+ const diff = await this.repository.getWorkingTreeDiff();
29
+ const intelligence = inspectRepository(this.repository.getRootPath(), keywords, [], []);
30
+ const project = await this.projectDetector.detectProject();
31
+ const expected = Math.max(1, Math.min(6, Math.ceil(Math.max(1, keywords.length) / 2)));
32
+ return {
33
+ task,
34
+ normalizedIntent: analyzer.normalizeTaskIntent(task),
35
+ keywords,
36
+ estimatedFiles: expected,
37
+ expectedSymbols: [1, Math.max(2, expected * 2)],
38
+ expectedTests: /fix|bug|retry|behavior|feature/i.test(task) ? 1 : 0,
39
+ expectsDependency: /dependency|package|library|sdk/i.test(task),
40
+ expectsPublicContract: /api|public|export|contract|interface/i.test(task),
41
+ likelyTouchedAreas: intelligence.reuseCandidates.slice(0, 5),
42
+ reuseCandidates: intelligence.reuseCandidates,
43
+ confidence: keywords.length > 1 ? "medium" : "low",
44
+ concerns: [
45
+ ...(project.isWorkspace ? ["Confirm the change stays inside the affected workspace package."] : []),
46
+ ...(keywords.length === 0 ? ["Task text is too vague for reliable intent matching."] : []),
47
+ ],
48
+ };
49
+ }
50
+
51
+ async analyze(
52
+ task: string,
53
+ configOverrides?: Partial<FranklyConfig>,
54
+ executedTests: ExecutedTest[] = [],
55
+ ): Promise<AnalysisResult> {
56
+ const startTime = Date.now();
57
+ const config = loadConfig({ ...this.config, ...configOverrides });
58
+ const fileDiffs = await this.repository.getWorkingTreeDiff();
59
+ const analyzer = new ChangeAnalyzer();
60
+ const symbolMap = new Map<string, ReturnType<SymbolExtractor["extractSymbols"]>>();
61
+ const degradedFindings = [];
62
+
63
+ for (const file of fileDiffs) {
64
+ if (file.status === "deleted" || !/\.[cm]?[jt]sx?$/.test(file.path)) continue;
65
+ try {
66
+ symbolMap.set(file.path, this.symbolExtractor.extractSymbols(path.join(this.repository.getRootPath(), file.path)));
67
+ } catch (error) {
68
+ symbolMap.set(file.path, []);
69
+ degradedFindings.push({
70
+ id: `semantic-fallback-${file.path}`,
71
+ severity: "warning" as const,
72
+ confidence: "high" as const,
73
+ title: "Semantic analysis unavailable",
74
+ description: `Semantic analysis unavailable for ${file.path}; using reduced-confidence diff evidence.`,
75
+ affectedSymbols: [file.path],
76
+ evidence: [],
77
+ });
78
+ }
79
+ }
80
+
81
+ const classifications = analyzer.classifyChanges({ task, files: fileDiffs, symbols: symbolMap });
82
+ const symbols = classifications
83
+ .map((item) => item.symbol)
84
+ .filter((item): item is ChangedSymbol => "filePath" in item);
85
+ const files: ChangedFile[] = fileDiffs.map((file) => ({
86
+ path: file.path,
87
+ changeType: file.status,
88
+ ...countDiffLines(file),
89
+ changes: file.hunks.length,
90
+ symbols: symbols.filter((symbol) => symbol.filePath === file.path),
91
+ }));
92
+ const intelligence = inspectRepository(
93
+ this.repository.getRootPath(),
94
+ analyzer.extractTaskKeywords(task),
95
+ fileDiffs,
96
+ symbols,
97
+ executedTests,
98
+ );
99
+ for (const symbol of symbols) {
100
+ symbol.callers = intelligence.directCallers.filter((file) => file !== symbol.filePath);
101
+ symbol.relatedTests = intelligence.predictedTests.filter((test) => test.path.includes(path.basename(symbol.filePath).replace(/\.[^.]+$/, ""))).map((test) => test.path);
102
+ }
103
+
104
+ const review = new RedInkReviewBuilder().build({
105
+ task,
106
+ files,
107
+ classifications,
108
+ scopeDrift: analyzer.detectScopeDrift(classifications.filter((item) => !("filePath" in item.symbol)), files.length),
109
+ fileCount: files.length,
110
+ symbolCount: symbols.length,
111
+ intelligence,
112
+ executedTests,
113
+ personality: config.personality,
114
+ action: config.action,
115
+ intensity: config.intensity,
116
+ disabled: config.trigger === "off" || config.intensity === "off",
117
+ maxCorrectionPasses: config.maxCorrectionPasses,
118
+ });
119
+ if (config.trigger !== "off" && config.intensity !== "off") review.findings.push(...degradedFindings);
120
+
121
+ return {
122
+ schemaVersion: "1",
123
+ repository: this.repository.getRootPath(),
124
+ task,
125
+ files,
126
+ symbols,
127
+ classifications,
128
+ findings: review.findings,
129
+ review,
130
+ timestamp: Date.now(),
131
+ duration: Date.now() - startTime,
132
+ };
133
+ }
134
+ }
135
+
136
+ export async function createAnalysisEngine(repositoryPath: string): Promise<FranklyAnalysisEngine> {
137
+ return new FranklyAnalysisEngine(
138
+ new Repository(repositoryPath),
139
+ new ProjectDetector(repositoryPath),
140
+ new SymbolExtractor(repositoryPath),
141
+ loadRepositoryConfig(repositoryPath),
142
+ );
143
+ }
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Core error types and utilities
3
+ */
4
+
5
+ export class FranklyError extends Error {
6
+ constructor(
7
+ message: string,
8
+ public code: string,
9
+ public details?: Record<string, unknown>,
10
+ ) {
11
+ super(message);
12
+ this.name = "FranklyError";
13
+ }
14
+ }
15
+
16
+ export class RepositoryError extends FranklyError {
17
+ constructor(message: string, details?: Record<string, unknown>) {
18
+ super(message, "REPOSITORY_ERROR", details);
19
+ this.name = "RepositoryError";
20
+ }
21
+ }
22
+
23
+ export class GitError extends FranklyError {
24
+ constructor(message: string, details?: Record<string, unknown>) {
25
+ super(message, "GIT_ERROR", details);
26
+ this.name = "GitError";
27
+ }
28
+ }
29
+
30
+ export class ProjectError extends FranklyError {
31
+ constructor(message: string, details?: Record<string, unknown>) {
32
+ super(message, "PROJECT_ERROR", details);
33
+ this.name = "ProjectError";
34
+ }
35
+ }
36
+
37
+ export class SemanticError extends FranklyError {
38
+ constructor(message: string, details?: Record<string, unknown>) {
39
+ super(message, "SEMANTIC_ERROR", details);
40
+ this.name = "SemanticError";
41
+ }
42
+ }
43
+
44
+ export class ConfigError extends FranklyError {
45
+ constructor(message: string, details?: Record<string, unknown>) {
46
+ super(message, "CONFIG_ERROR", details);
47
+ this.name = "ConfigError";
48
+ }
49
+ }
50
+
51
+ export class AnalysisError extends FranklyError {
52
+ constructor(message: string, details?: Record<string, unknown>) {
53
+ super(message, "ANALYSIS_ERROR", details);
54
+ this.name = "AnalysisError";
55
+ }
56
+ }
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Core module exports
3
+ */
4
+
5
+ export * from "./models.js";
6
+ export * from "./config.js";
7
+ export * from "./errors.js";
8
+ export * from "./engine.js";
@@ -0,0 +1,280 @@
1
+ /**
2
+ * Core data models for Frankly
3
+ * Foundation types for all analysis
4
+ */
5
+
6
+ // ============================================================================
7
+ // Change Symbols and Files
8
+ // ============================================================================
9
+
10
+ export type SymbolType =
11
+ | "function"
12
+ | "method"
13
+ | "class"
14
+ | "interface"
15
+ | "type"
16
+ | "enum"
17
+ | "variable"
18
+ | "constant"
19
+ | "export"
20
+ | "route"
21
+ | "controller"
22
+ | "service"
23
+ | "hook"
24
+ | "component"
25
+ | "schema"
26
+ | "configuration";
27
+
28
+ export type ChangeType = "added" | "modified" | "deleted" | "renamed";
29
+
30
+ export type Visibility = "public" | "private" | "protected" | "internal";
31
+
32
+ export interface ChangedSymbol {
33
+ id: string;
34
+ name: string;
35
+ type: SymbolType;
36
+ filePath: string;
37
+ lineRange: [number, number]; // [start, end]
38
+ changeType: ChangeType;
39
+ visibility: Visibility;
40
+ isExported: boolean;
41
+ callers: string[];
42
+ callees: string[];
43
+ implementations: string[];
44
+ interfaces: string[];
45
+ referencedContracts: string[];
46
+ relatedTests: string[];
47
+ packageName?: string;
48
+ runtimeBoundary?: string;
49
+ evidence: Evidence[];
50
+ }
51
+
52
+ export interface ChangedFile {
53
+ path: string;
54
+ changeType: ChangeType;
55
+ additions: number;
56
+ deletions: number;
57
+ changes: number;
58
+ symbols: ChangedSymbol[];
59
+ }
60
+
61
+ // ============================================================================
62
+ // Evidence Model
63
+ // ============================================================================
64
+
65
+ export type EvidenceType =
66
+ | "TASK_MATCH"
67
+ | "SYMBOL_REFERENCE"
68
+ | "CALLER_RELATION"
69
+ | "CALLEE_RELATION"
70
+ | "INTERFACE_IMPLEMENTATION"
71
+ | "IMPORT_DEPENDENCY"
72
+ | "EXPORT_CONTRACT"
73
+ | "TEST_REFERENCE"
74
+ | "CONFIG_REFERENCE"
75
+ | "RUNTIME_BOUNDARY"
76
+ | "GIT_COCHANGE"
77
+ | "SIMILAR_IMPLEMENTATION"
78
+ | "EXISTING_UTILITY"
79
+ | "DEPENDENCY_USAGE"
80
+ | "TYPE_REQUIREMENT"
81
+ | "COMPILE_REQUIREMENT"
82
+ | "DIFF_DEPENDENT"
83
+ | "HISTORICAL_PATTERN";
84
+
85
+ export type Confidence = "high" | "medium" | "low";
86
+
87
+ export interface Evidence {
88
+ type: EvidenceType;
89
+ confidence: Confidence;
90
+ explanation: string;
91
+ reference?: string;
92
+ }
93
+
94
+ // ============================================================================
95
+ // Change Classification
96
+ // ============================================================================
97
+
98
+ export type ChangeClassification =
99
+ | "REQUIRED"
100
+ | "SUPPORTING"
101
+ | "TEST"
102
+ | "REFACTOR"
103
+ | "INCIDENTAL"
104
+ | "GENERATED"
105
+ | "SUSPICIOUS"
106
+ | "UNEXPLAINED";
107
+
108
+ export interface ClassifiedChange {
109
+ symbol: ChangedSymbol | ChangedFile;
110
+ classification: ChangeClassification;
111
+ confidence: Confidence;
112
+ justification: string;
113
+ evidence: Evidence[];
114
+ }
115
+
116
+ // ============================================================================
117
+ // Behavioral Changes
118
+ // ============================================================================
119
+
120
+ export type BehavioralChangeType =
121
+ | "API_BEHAVIOR"
122
+ | "CONTROL_FLOW"
123
+ | "ERROR_HANDLING"
124
+ | "STATE_MUTATION"
125
+ | "PERSISTENCE"
126
+ | "SECURITY"
127
+ | "AUTHORIZATION"
128
+ | "AUTHENTICATION"
129
+ | "CONCURRENCY"
130
+ | "RETRY"
131
+ | "CACHE"
132
+ | "CONFIGURATION"
133
+ | "DEPENDENCY"
134
+ | "DATABASE_SCHEMA"
135
+ | "PUBLIC_CONTRACT"
136
+ | "EVENT"
137
+ | "MESSAGE_SCHEMA"
138
+ | "TEST_ONLY"
139
+ | "REFACTOR_ONLY";
140
+
141
+ // ============================================================================
142
+ // Contract Changes
143
+ // ============================================================================
144
+
145
+ export type ContractChangeType =
146
+ | "ADDED"
147
+ | "REMOVED"
148
+ | "WIDENED"
149
+ | "NARROWED"
150
+ | "CHANGED";
151
+
152
+ export interface ContractChange {
153
+ type: ContractChangeType;
154
+ location: string;
155
+ oldSignature?: string;
156
+ newSignature?: string;
157
+ severity: "breaking" | "potentially-breaking" | "compatible";
158
+ confidence: Confidence;
159
+ affectedCallers?: string[];
160
+ }
161
+
162
+ // ============================================================================
163
+ // Findings and Recommendations
164
+ // ============================================================================
165
+
166
+ export type FindingSeverity = "error" | "warning" | "info";
167
+
168
+ export interface Finding {
169
+ id: string;
170
+ severity: FindingSeverity;
171
+ confidence: Confidence;
172
+ title: string;
173
+ description: string;
174
+ affectedSymbols: string[];
175
+ evidence: Evidence[];
176
+ recommendation?: string;
177
+ autoCorrectible?: boolean;
178
+ }
179
+
180
+ // ============================================================================
181
+ // Verdict and Review
182
+ // ============================================================================
183
+
184
+ export type Verdict = "CLEAN" | "REVIEW" | "SIMPLIFY" | "SPLIT" | "VERIFY" | "BLOCK";
185
+
186
+ export interface RedInkReview {
187
+ task: string;
188
+ verdict: Verdict;
189
+ taskAlignment: "high" | "medium" | "low";
190
+ changeNecessity: number; // 0-100
191
+ changeSurface: "LOW" | "MEDIUM" | "HIGH";
192
+ scopeDrift: number; // 0-1 (percentage)
193
+ findings: Finding[];
194
+ blastRadius: {
195
+ directCallers: number;
196
+ transitiveCallers: number;
197
+ affectedPackages: number;
198
+ callers: string[];
199
+ packages: string[];
200
+ };
201
+ testImpact: {
202
+ likelyAffected: number;
203
+ possiblyAffected: number;
204
+ executed: number;
205
+ passed: number;
206
+ predicted: PredictedTest[];
207
+ executedResults: ExecutedTest[];
208
+ };
209
+ contractChanges: ContractChange[];
210
+ behavioralChanges: BehavioralChangeType[];
211
+ reuseCandidates: string[];
212
+ correction?: CorrectionPlan;
213
+ recommendation: string;
214
+ personality?: string;
215
+ }
216
+
217
+ // ============================================================================
218
+ // Analysis Result
219
+ // ============================================================================
220
+
221
+ export interface AnalysisResult {
222
+ schemaVersion: "1";
223
+ repository: string;
224
+ task: string;
225
+ files: ChangedFile[];
226
+ symbols: ChangedSymbol[];
227
+ classifications: ClassifiedChange[];
228
+ findings: Finding[];
229
+ review: RedInkReview;
230
+ timestamp: number;
231
+ duration: number; // ms
232
+ }
233
+
234
+ export interface TaskPlan {
235
+ task: string;
236
+ normalizedIntent: string;
237
+ keywords: string[];
238
+ estimatedFiles: number;
239
+ expectedSymbols: [number, number];
240
+ expectedTests: number;
241
+ expectsDependency: boolean;
242
+ expectsPublicContract: boolean;
243
+ likelyTouchedAreas: string[];
244
+ reuseCandidates: string[];
245
+ confidence: "high" | "medium" | "low";
246
+ concerns: string[];
247
+ }
248
+
249
+ // ============================================================================
250
+ // Test Impact
251
+ // ============================================================================
252
+
253
+ export type PredictedTestStatus = "LIKELY_AFFECTED" | "POSSIBLY_AFFECTED" | "UNLIKELY_AFFECTED";
254
+ export type ExecutedTestStatus = "PASSED" | "FAILED" | "SKIPPED" | "NOT_RUN";
255
+
256
+ export interface PredictedTest {
257
+ path: string;
258
+ name: string;
259
+ status: PredictedTestStatus;
260
+ evidence: Evidence[];
261
+ }
262
+
263
+ export interface ExecutedTest {
264
+ path: string;
265
+ name: string;
266
+ status: ExecutedTestStatus;
267
+ duration?: number;
268
+ }
269
+
270
+ // ============================================================================
271
+ // Correction Plan
272
+ // ============================================================================
273
+
274
+ export interface CorrectionPlan {
275
+ pass: number;
276
+ removals: string[]; // symbol IDs
277
+ simplifications: { symbol: string; suggestion: string }[];
278
+ evidence: Evidence[];
279
+ instruction: string;
280
+ }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Core module exports
3
+ */
4
+
5
+ export { Repository, createRepository } from "./repository.js";
6
+ export type { FileDiff, DiffHunk } from "./repository.js";
7
+ export { countDiffLines } from "./repository.js";