edsger 0.51.0 → 0.52.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.
- package/.claude/settings.local.json +23 -3
- package/.env.local +12 -0
- package/dist/commands/find-smells/index.d.ts +21 -0
- package/dist/commands/find-smells/index.js +65 -0
- package/dist/index.js +29 -0
- package/dist/phases/find-bugs/index.js +7 -92
- package/dist/phases/find-bugs/state.d.ts +10 -35
- package/dist/phases/find-bugs/state.js +12 -120
- package/dist/phases/find-features/index.js +16 -83
- package/dist/phases/find-features/prompts.d.ts +7 -1
- package/dist/phases/find-features/prompts.js +31 -11
- package/dist/phases/find-features/state.d.ts +15 -19
- package/dist/phases/find-features/state.js +17 -89
- package/dist/phases/find-features/types.d.ts +1 -1
- package/dist/phases/find-shared/git.d.ts +24 -0
- package/dist/phases/find-shared/git.js +60 -0
- package/dist/phases/find-shared/mcp.d.ts +33 -0
- package/dist/phases/find-shared/mcp.js +69 -0
- package/dist/phases/find-shared/scan-state.d.ts +33 -0
- package/dist/phases/find-shared/scan-state.js +112 -0
- package/dist/phases/find-smells/index.d.ts +47 -0
- package/dist/phases/find-smells/index.js +278 -0
- package/dist/phases/find-smells/prompts.d.ts +30 -0
- package/dist/phases/find-smells/prompts.js +129 -0
- package/dist/phases/find-smells/state.d.ts +21 -0
- package/dist/phases/find-smells/state.js +17 -0
- package/dist/phases/find-smells/types.d.ts +51 -0
- package/dist/phases/find-smells/types.js +64 -0
- package/package.json +1 -1
- package/vitest.config.ts +2 -0
- package/dist/api/__tests__/app-store.test.d.ts +0 -7
- package/dist/api/__tests__/app-store.test.js +0 -60
- package/dist/api/__tests__/intelligence.test.d.ts +0 -11
- package/dist/api/__tests__/intelligence.test.js +0 -315
- package/dist/api/features/__tests__/feature-utils.test.d.ts +0 -4
- package/dist/api/features/__tests__/feature-utils.test.js +0 -370
- package/dist/api/features/__tests__/status-updater.test.d.ts +0 -4
- package/dist/api/features/__tests__/status-updater.test.js +0 -88
- package/dist/api/features/approval-checker.d.ts +0 -20
- package/dist/api/features/approval-checker.js +0 -152
- package/dist/api/features/batch-operations.d.ts +0 -17
- package/dist/api/features/batch-operations.js +0 -100
- package/dist/api/features/feature-utils.d.ts +0 -23
- package/dist/api/features/feature-utils.js +0 -80
- package/dist/api/features/get-feature.d.ts +0 -5
- package/dist/api/features/get-feature.js +0 -21
- package/dist/api/features/index.d.ts +0 -8
- package/dist/api/features/index.js +0 -10
- package/dist/api/features/status-updater.d.ts +0 -41
- package/dist/api/features/status-updater.js +0 -122
- package/dist/api/features/test-cases.d.ts +0 -29
- package/dist/api/features/test-cases.js +0 -110
- package/dist/api/features/update-feature.d.ts +0 -20
- package/dist/api/features/update-feature.js +0 -83
- package/dist/api/features/user-stories.d.ts +0 -21
- package/dist/api/features/user-stories.js +0 -88
- package/dist/commands/agent-workflow/feature-worker.d.ts +0 -14
- package/dist/commands/agent-workflow/feature-worker.js +0 -65
- package/dist/commands/build/__tests__/build.test.d.ts +0 -5
- package/dist/commands/build/__tests__/build.test.js +0 -206
- package/dist/commands/build/__tests__/detect-project.test.d.ts +0 -6
- package/dist/commands/build/__tests__/detect-project.test.js +0 -160
- package/dist/commands/build/__tests__/run-build.test.d.ts +0 -6
- package/dist/commands/build/__tests__/run-build.test.js +0 -433
- package/dist/commands/intelligence/__tests__/command.test.d.ts +0 -4
- package/dist/commands/intelligence/__tests__/command.test.js +0 -48
- package/dist/commands/workflow/core/__tests__/feature-filter.test.d.ts +0 -5
- package/dist/commands/workflow/core/__tests__/feature-filter.test.js +0 -316
- package/dist/commands/workflow/core/__tests__/pipeline-evaluator.test.d.ts +0 -4
- package/dist/commands/workflow/core/__tests__/pipeline-evaluator.test.js +0 -397
- package/dist/commands/workflow/core/__tests__/state-manager.test.d.ts +0 -4
- package/dist/commands/workflow/core/__tests__/state-manager.test.js +0 -384
- package/dist/commands/workflow/core/feature-filter.d.ts +0 -16
- package/dist/commands/workflow/core/feature-filter.js +0 -47
- package/dist/commands/workflow/feature-coordinator.d.ts +0 -18
- package/dist/commands/workflow/feature-coordinator.js +0 -161
- package/dist/config/__tests__/config.test.d.ts +0 -4
- package/dist/config/__tests__/config.test.js +0 -286
- package/dist/config/__tests__/feature-status.test.d.ts +0 -4
- package/dist/config/__tests__/feature-status.test.js +0 -111
- package/dist/config/feature-status.d.ts +0 -56
- package/dist/config/feature-status.js +0 -130
- package/dist/errors/__tests__/index.test.d.ts +0 -4
- package/dist/errors/__tests__/index.test.js +0 -349
- package/dist/phases/app-store-generation/__tests__/agent.test.d.ts +0 -5
- package/dist/phases/app-store-generation/__tests__/agent.test.js +0 -142
- package/dist/phases/app-store-generation/__tests__/context.test.d.ts +0 -4
- package/dist/phases/app-store-generation/__tests__/context.test.js +0 -284
- package/dist/phases/app-store-generation/__tests__/prompts.test.d.ts +0 -4
- package/dist/phases/app-store-generation/__tests__/prompts.test.js +0 -122
- package/dist/phases/app-store-generation/__tests__/screenshot-composer.test.d.ts +0 -5
- package/dist/phases/app-store-generation/__tests__/screenshot-composer.test.js +0 -826
- package/dist/phases/code-review/__tests__/diff-utils.test.d.ts +0 -1
- package/dist/phases/code-review/__tests__/diff-utils.test.js +0 -101
- package/dist/phases/feature-analysis/agent.d.ts +0 -13
- package/dist/phases/feature-analysis/agent.js +0 -112
- package/dist/phases/feature-analysis/context.d.ts +0 -24
- package/dist/phases/feature-analysis/context.js +0 -138
- package/dist/phases/feature-analysis/index.d.ts +0 -8
- package/dist/phases/feature-analysis/index.js +0 -199
- package/dist/phases/feature-analysis/outcome.d.ts +0 -40
- package/dist/phases/feature-analysis/outcome.js +0 -280
- package/dist/phases/feature-analysis/prompts.d.ts +0 -10
- package/dist/phases/feature-analysis/prompts.js +0 -212
- package/dist/phases/feature-analysis-verification/agent.d.ts +0 -33
- package/dist/phases/feature-analysis-verification/agent.js +0 -124
- package/dist/phases/feature-analysis-verification/index.d.ts +0 -25
- package/dist/phases/feature-analysis-verification/index.js +0 -92
- package/dist/phases/feature-analysis-verification/prompts.d.ts +0 -10
- package/dist/phases/feature-analysis-verification/prompts.js +0 -100
- package/dist/phases/intelligence-analysis/__tests__/context.test.d.ts +0 -4
- package/dist/phases/intelligence-analysis/__tests__/context.test.js +0 -192
- package/dist/phases/intelligence-analysis/__tests__/matching.test.d.ts +0 -13
- package/dist/phases/intelligence-analysis/__tests__/matching.test.js +0 -154
- package/dist/phases/intelligence-analysis/__tests__/orchestration.test.d.ts +0 -5
- package/dist/phases/intelligence-analysis/__tests__/orchestration.test.js +0 -378
- package/dist/phases/intelligence-analysis/__tests__/prompts.test.d.ts +0 -4
- package/dist/phases/intelligence-analysis/__tests__/prompts.test.js +0 -33
- package/dist/phases/pr-execution/__tests__/file-assigner.test.d.ts +0 -1
- package/dist/phases/pr-execution/__tests__/file-assigner.test.js +0 -303
- package/dist/phases/pr-resolve/__tests__/checklist-learner.test.d.ts +0 -1
- package/dist/phases/pr-resolve/__tests__/checklist-learner.test.js +0 -157
- package/dist/phases/pr-resolve/__tests__/prompts.test.d.ts +0 -1
- package/dist/phases/pr-resolve/__tests__/prompts.test.js +0 -116
- package/dist/phases/pr-resolve/__tests__/resolve-mapping.test.d.ts +0 -1
- package/dist/phases/pr-resolve/__tests__/resolve-mapping.test.js +0 -138
- package/dist/phases/pr-resolve/__tests__/types.test.d.ts +0 -1
- package/dist/phases/pr-resolve/__tests__/types.test.js +0 -43
- package/dist/phases/pr-resolve/__tests__/workspace.test.d.ts +0 -1
- package/dist/phases/pr-resolve/__tests__/workspace.test.js +0 -111
- package/dist/phases/pr-review/__tests__/prompts.test.d.ts +0 -1
- package/dist/phases/pr-review/__tests__/prompts.test.js +0 -49
- package/dist/phases/pr-review/__tests__/review-comments.test.d.ts +0 -1
- package/dist/phases/pr-review/__tests__/review-comments.test.js +0 -110
- package/dist/phases/pr-shared/__tests__/agent-utils.test.d.ts +0 -1
- package/dist/phases/pr-shared/__tests__/agent-utils.test.js +0 -91
- package/dist/phases/pr-shared/__tests__/context.test.d.ts +0 -1
- package/dist/phases/pr-shared/__tests__/context.test.js +0 -94
- package/dist/phases/pr-splitting/__tests__/import-dep-validator.test.d.ts +0 -1
- package/dist/phases/pr-splitting/__tests__/import-dep-validator.test.js +0 -331
- package/dist/phases/run-sheet/render.d.ts +0 -60
- package/dist/phases/run-sheet/render.js +0 -297
- package/dist/phases/smoke-test/__tests__/agent.test.d.ts +0 -4
- package/dist/phases/smoke-test/__tests__/agent.test.js +0 -84
- package/dist/phases/smoke-test/__tests__/github.test.d.ts +0 -9
- package/dist/phases/smoke-test/__tests__/github.test.js +0 -120
- package/dist/phases/smoke-test/__tests__/snapshot.test.d.ts +0 -8
- package/dist/phases/smoke-test/__tests__/snapshot.test.js +0 -93
- package/dist/phases/smoke-test/github.d.ts +0 -54
- package/dist/phases/smoke-test/github.js +0 -101
- package/dist/phases/smoke-test/snapshot.d.ts +0 -27
- package/dist/phases/smoke-test/snapshot.js +0 -157
- package/dist/services/coaching/__tests__/coaching-agent.test.d.ts +0 -1
- package/dist/services/coaching/__tests__/coaching-agent.test.js +0 -74
- package/dist/services/coaching/__tests__/coaching-loop.test.d.ts +0 -1
- package/dist/services/coaching/__tests__/coaching-loop.test.js +0 -59
- package/dist/services/coaching/__tests__/self-rating.test.d.ts +0 -1
- package/dist/services/coaching/__tests__/self-rating.test.js +0 -188
- package/dist/services/lifecycle-agent/__tests__/phase-criteria.test.d.ts +0 -4
- package/dist/services/lifecycle-agent/__tests__/phase-criteria.test.js +0 -133
- package/dist/services/lifecycle-agent/__tests__/transition-rules.test.d.ts +0 -4
- package/dist/services/lifecycle-agent/__tests__/transition-rules.test.js +0 -336
- package/dist/services/lifecycle-agent/index.d.ts +0 -24
- package/dist/services/lifecycle-agent/index.js +0 -25
- package/dist/services/lifecycle-agent/phase-criteria.d.ts +0 -57
- package/dist/services/lifecycle-agent/phase-criteria.js +0 -335
- package/dist/services/lifecycle-agent/transition-rules.d.ts +0 -60
- package/dist/services/lifecycle-agent/transition-rules.js +0 -184
- package/dist/services/lifecycle-agent/types.d.ts +0 -190
- package/dist/services/lifecycle-agent/types.js +0 -12
- package/dist/services/phase-hooks/__tests__/bindings-fetcher.test.d.ts +0 -1
- package/dist/services/phase-hooks/__tests__/bindings-fetcher.test.js +0 -122
- package/dist/services/phase-hooks/__tests__/hook-executor.test.d.ts +0 -1
- package/dist/services/phase-hooks/__tests__/hook-executor.test.js +0 -321
- package/dist/services/phase-hooks/__tests__/hook-runner.test.d.ts +0 -1
- package/dist/services/phase-hooks/__tests__/hook-runner.test.js +0 -261
- package/dist/services/phase-hooks/__tests__/plugin-loader.test.d.ts +0 -1
- package/dist/services/phase-hooks/__tests__/plugin-loader.test.js +0 -158
- package/dist/services/video/__tests__/video-pipeline.test.d.ts +0 -6
- package/dist/services/video/__tests__/video-pipeline.test.js +0 -249
- package/dist/types/features.d.ts +0 -35
- package/dist/types/features.js +0 -1
- package/dist/workspace/__tests__/workspace-manager.test.d.ts +0 -7
- package/dist/workspace/__tests__/workspace-manager.test.js +0 -52
|
@@ -1,8 +1,28 @@
|
|
|
1
1
|
{
|
|
2
2
|
"permissions": {
|
|
3
3
|
"allow": [
|
|
4
|
-
"
|
|
5
|
-
"Bash(npm run
|
|
6
|
-
|
|
4
|
+
"Read(//Users/steven/development/edsger/**)",
|
|
5
|
+
"Bash(npm run build)",
|
|
6
|
+
"Bash(node:*)",
|
|
7
|
+
"Bash(git add:*)",
|
|
8
|
+
"Bash(git commit:*)",
|
|
9
|
+
"Bash(ls:*)",
|
|
10
|
+
"Bash(cat:*)",
|
|
11
|
+
"Bash(npm run typecheck:*)",
|
|
12
|
+
"Bash(git diff:*)",
|
|
13
|
+
"WebSearch",
|
|
14
|
+
"WebFetch(domain:supabase.com)",
|
|
15
|
+
"Bash(npm install:*)",
|
|
16
|
+
"Bash(grep:*)",
|
|
17
|
+
"Bash(npx supabase gen types typescript --help:*)",
|
|
18
|
+
"Bash(git -C /Users/steven/development/edsger status)",
|
|
19
|
+
"Bash(git -C /Users/steven/development/edsger diff)",
|
|
20
|
+
"Bash(git -C /Users/steven/development/edsger log --oneline -5)",
|
|
21
|
+
"Bash(git -C /Users/steven/development/edsger add supabase/migrations/20251231000000_drop_unused_views.sql)",
|
|
22
|
+
"Bash(git -C /Users/steven/development/edsger commit -m \"$\\(cat <<''EOF''\nchore: drop unused database views\n\nRemove test_report_summary and user_stories_with_context views that are defined but never used in the application.\n\n🤖 Generated with [Claude Code]\\(https://claude.com/claude-code\\)\n\nCo-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>\nEOF\n\\)\")",
|
|
23
|
+
"Bash(git -C /Users/steven/development/edsger commit -m \"$\\(cat <<''EOF''\nchore: drop unused database views\n\nRemove test_report_summary and user_stories_with_context views\nthat are defined but never used in the application.\n\n🤖 Generated with [Claude Code]\\(https://claude.com/claude-code\\)\n\nCo-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>\nEOF\n\\)\")"
|
|
24
|
+
],
|
|
25
|
+
"deny": [],
|
|
26
|
+
"ask": []
|
|
7
27
|
}
|
|
8
28
|
}
|
package/.env.local
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI command: edsger find-smells <productId>
|
|
3
|
+
* Audits the product's repository for code smells (refactor candidates, perf
|
|
4
|
+
* cliffs, dead code, etc.) and files each new finding as an issue.
|
|
5
|
+
*/
|
|
6
|
+
import { type SmellCategory } from '../../phases/find-smells/types.js';
|
|
7
|
+
export interface FindSmellsCliOptions {
|
|
8
|
+
full?: boolean;
|
|
9
|
+
branch?: string;
|
|
10
|
+
maxFiles?: number;
|
|
11
|
+
categories?: SmellCategory[];
|
|
12
|
+
verbose?: boolean;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Parse and validate `--categories` from the CLI. Throws on invalid input so
|
|
16
|
+
* commander's option-parser rejects the run before any work starts. Empty
|
|
17
|
+
* input is treated as "no filter" (returns undefined) rather than an error —
|
|
18
|
+
* matches user intuition for `--categories=`.
|
|
19
|
+
*/
|
|
20
|
+
export declare function parseCategoriesOption(raw: string): SmellCategory[] | undefined;
|
|
21
|
+
export declare function runFindSmells(productId: string, options: FindSmellsCliOptions): Promise<void>;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI command: edsger find-smells <productId>
|
|
3
|
+
* Audits the product's repository for code smells (refactor candidates, perf
|
|
4
|
+
* cliffs, dead code, etc.) and files each new finding as an issue.
|
|
5
|
+
*/
|
|
6
|
+
import { getGitHubConfigByProduct } from '../../api/github.js';
|
|
7
|
+
import { scanForSmells } from '../../phases/find-smells/index.js';
|
|
8
|
+
import { isSmellCategory, SMELL_CATEGORIES, } from '../../phases/find-smells/types.js';
|
|
9
|
+
import { logError, logInfo, logSuccess } from '../../utils/logger.js';
|
|
10
|
+
/**
|
|
11
|
+
* Parse and validate `--categories` from the CLI. Throws on invalid input so
|
|
12
|
+
* commander's option-parser rejects the run before any work starts. Empty
|
|
13
|
+
* input is treated as "no filter" (returns undefined) rather than an error —
|
|
14
|
+
* matches user intuition for `--categories=`.
|
|
15
|
+
*/
|
|
16
|
+
export function parseCategoriesOption(raw) {
|
|
17
|
+
const tokens = raw
|
|
18
|
+
.split(',')
|
|
19
|
+
.map((s) => s.trim())
|
|
20
|
+
.filter((s) => s.length > 0);
|
|
21
|
+
if (tokens.length === 0) {
|
|
22
|
+
return undefined;
|
|
23
|
+
}
|
|
24
|
+
const invalid = tokens.filter((t) => !isSmellCategory(t));
|
|
25
|
+
if (invalid.length > 0) {
|
|
26
|
+
throw new Error(`--categories: unknown value(s): ${invalid.join(', ')}. ` +
|
|
27
|
+
`Allowed: ${SMELL_CATEGORIES.join(', ')}`);
|
|
28
|
+
}
|
|
29
|
+
// Dedup while preserving order so the error message and downstream behaviour
|
|
30
|
+
// are deterministic.
|
|
31
|
+
return Array.from(new Set(tokens));
|
|
32
|
+
}
|
|
33
|
+
export async function runFindSmells(productId, options) {
|
|
34
|
+
const { full, branch, maxFiles, categories, verbose } = options;
|
|
35
|
+
logInfo(`Starting smell scan for product ${productId}`);
|
|
36
|
+
const githubConfig = await getGitHubConfigByProduct(productId, verbose);
|
|
37
|
+
if (!githubConfig.configured ||
|
|
38
|
+
!githubConfig.token ||
|
|
39
|
+
!githubConfig.owner ||
|
|
40
|
+
!githubConfig.repo) {
|
|
41
|
+
logError(`GitHub not configured for product ${productId}: ${githubConfig.message || 'No installation found'}`);
|
|
42
|
+
process.exit(1);
|
|
43
|
+
}
|
|
44
|
+
const result = await scanForSmells({
|
|
45
|
+
productId,
|
|
46
|
+
githubToken: githubConfig.token,
|
|
47
|
+
owner: githubConfig.owner,
|
|
48
|
+
repo: githubConfig.repo,
|
|
49
|
+
full,
|
|
50
|
+
branch,
|
|
51
|
+
maxFiles,
|
|
52
|
+
categories,
|
|
53
|
+
verbose,
|
|
54
|
+
});
|
|
55
|
+
if (result.status === 'success') {
|
|
56
|
+
logSuccess(result.message);
|
|
57
|
+
if (result.summary) {
|
|
58
|
+
logInfo(result.summary);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
else {
|
|
62
|
+
logError(result.message);
|
|
63
|
+
process.exit(1);
|
|
64
|
+
}
|
|
65
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -15,6 +15,7 @@ import { runCodeReview } from './commands/code-review/index.js';
|
|
|
15
15
|
import { runConfigGet, runConfigList, runConfigSet, runConfigUnset, } from './commands/config/index.js';
|
|
16
16
|
import { runFindBugs } from './commands/find-bugs/index.js';
|
|
17
17
|
import { runFindFeatures } from './commands/find-features/index.js';
|
|
18
|
+
import { parseCategoriesOption, runFindSmells, } from './commands/find-smells/index.js';
|
|
18
19
|
import { runGrowthAnalysis } from './commands/growth-analysis/index.js';
|
|
19
20
|
import { runInit } from './commands/init/index.js';
|
|
20
21
|
import { runIntelligence } from './commands/intelligence/index.js';
|
|
@@ -26,6 +27,8 @@ import { runRunSheetCommand } from './commands/run-sheet/index.js';
|
|
|
26
27
|
import { runSmokeTestCommand } from './commands/smoke-test/index.js';
|
|
27
28
|
import { runTaskWorker } from './commands/task-worker/index.js';
|
|
28
29
|
import { runWorkflow } from './commands/workflow/index.js';
|
|
30
|
+
import { DEFAULT_MAX_FILES as FIND_SMELLS_DEFAULT_MAX_FILES } from './phases/find-smells/index.js';
|
|
31
|
+
import { SMELL_CATEGORIES, } from './phases/find-smells/types.js';
|
|
29
32
|
import { logError, logInfo } from './utils/logger.js';
|
|
30
33
|
// Get package.json version dynamically
|
|
31
34
|
// eslint-disable-next-line @typescript-eslint/naming-convention -- ESM __filename/__dirname polyfill
|
|
@@ -414,6 +417,32 @@ program
|
|
|
414
417
|
}
|
|
415
418
|
});
|
|
416
419
|
// ============================================================
|
|
420
|
+
// Subcommand: edsger find-smells <productId>
|
|
421
|
+
// ============================================================
|
|
422
|
+
program
|
|
423
|
+
.command('find-smells <productId>')
|
|
424
|
+
.description("AI-audit a product's repository for code smells (refactor candidates, perf cliffs, dead code, type-safety gaps) and file each new finding as an issue")
|
|
425
|
+
.option('--full', 'Force a full scan even if previous-scan state exists')
|
|
426
|
+
.option('--branch <name>', 'Branch to scan (defaults to repo default branch)')
|
|
427
|
+
.option('--max-files <n>', `Upper bound on files the auditor may Read (default ${FIND_SMELLS_DEFAULT_MAX_FILES})`, (value) => {
|
|
428
|
+
const n = parseInt(value, 10);
|
|
429
|
+
if (Number.isNaN(n) || n <= 0) {
|
|
430
|
+
throw new Error('--max-files must be a positive integer');
|
|
431
|
+
}
|
|
432
|
+
return n;
|
|
433
|
+
})
|
|
434
|
+
.option('--categories <list>', `Comma-separated category filter (${SMELL_CATEGORIES.join(',')})`, parseCategoriesOption)
|
|
435
|
+
.option('-v, --verbose', 'Verbose output')
|
|
436
|
+
.action(async (productId, opts) => {
|
|
437
|
+
try {
|
|
438
|
+
await runFindSmells(productId, opts);
|
|
439
|
+
}
|
|
440
|
+
catch (error) {
|
|
441
|
+
logError(error instanceof Error ? error.message : String(error));
|
|
442
|
+
process.exit(1);
|
|
443
|
+
}
|
|
444
|
+
});
|
|
445
|
+
// ============================================================
|
|
417
446
|
// Subcommand: edsger pr-resolve <productId>
|
|
418
447
|
// ============================================================
|
|
419
448
|
program
|
|
@@ -4,11 +4,11 @@
|
|
|
4
4
|
* incremental, scoped to commits since the previous successful scan.
|
|
5
5
|
*/
|
|
6
6
|
import { query } from '@anthropic-ai/claude-agent-sdk';
|
|
7
|
-
import { execFileSync } from 'child_process';
|
|
8
|
-
import { callMcpEndpoint } from '../../api/mcp-client.js';
|
|
9
7
|
import { DEFAULT_MODEL } from '../../constants.js';
|
|
10
8
|
import { logError, logInfo, logSuccess, logWarning, } from '../../utils/logger.js';
|
|
11
9
|
import { cloneIssueRepo, ensureWorkspaceDir, syncRepoToRef, } from '../../workspace/workspace-manager.js';
|
|
10
|
+
import { detectDefaultBranch, gitRevParse, isAncestor, listChangedPaths, } from '../find-shared/git.js';
|
|
11
|
+
import { createIssue, fetchOpenIssues, fetchProductBasics, } from '../find-shared/mcp.js';
|
|
12
12
|
import { createPromptGenerator, extractTextFromContent, tryExtractResult, } from '../pr-shared/agent-utils.js';
|
|
13
13
|
import { createFindBugsSystemPrompt, createFindBugsUserPrompt, } from './prompts.js';
|
|
14
14
|
import { acquireFindBugsLock, loadFindBugsState, updateFindBugsState, } from './state.js';
|
|
@@ -193,97 +193,12 @@ export async function scanForBugs(options) {
|
|
|
193
193
|
lock.release();
|
|
194
194
|
}
|
|
195
195
|
}
|
|
196
|
-
function gitRevParse(repoPath, ref) {
|
|
197
|
-
return execFileSync('git', ['rev-parse', ref], {
|
|
198
|
-
cwd: repoPath,
|
|
199
|
-
encoding: 'utf-8',
|
|
200
|
-
}).trim();
|
|
201
|
-
}
|
|
202
|
-
function detectDefaultBranch(repoPath) {
|
|
203
|
-
try {
|
|
204
|
-
const ref = execFileSync('git', ['symbolic-ref', '--short', 'refs/remotes/origin/HEAD'], { cwd: repoPath, encoding: 'utf-8' }).trim();
|
|
205
|
-
// ref is like "origin/main"
|
|
206
|
-
return ref.replace(/^origin\//, '');
|
|
207
|
-
}
|
|
208
|
-
catch {
|
|
209
|
-
return 'main';
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
function isAncestor(repoPath, ancestor, descendant) {
|
|
213
|
-
try {
|
|
214
|
-
execFileSync('git', ['merge-base', '--is-ancestor', ancestor, descendant], {
|
|
215
|
-
cwd: repoPath,
|
|
216
|
-
stdio: 'pipe',
|
|
217
|
-
});
|
|
218
|
-
return true;
|
|
219
|
-
}
|
|
220
|
-
catch {
|
|
221
|
-
return false;
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
function listChangedPaths(repoPath, baseSha, headSha) {
|
|
225
|
-
try {
|
|
226
|
-
const out = execFileSync('git', ['diff', '--name-only', `${baseSha}..${headSha}`], { cwd: repoPath, encoding: 'utf-8' });
|
|
227
|
-
return out
|
|
228
|
-
.split('\n')
|
|
229
|
-
.map((s) => s.trim())
|
|
230
|
-
.filter((s) => s.length > 0);
|
|
231
|
-
}
|
|
232
|
-
catch {
|
|
233
|
-
return [];
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
async function fetchProductBasics(productId) {
|
|
237
|
-
try {
|
|
238
|
-
const result = (await callMcpEndpoint('resources/read', {
|
|
239
|
-
uri: `product://${productId}`,
|
|
240
|
-
}));
|
|
241
|
-
const text = result.contents?.[0]?.text || '{}';
|
|
242
|
-
const parsed = JSON.parse(text);
|
|
243
|
-
return {
|
|
244
|
-
name: parsed.name || productId,
|
|
245
|
-
description: parsed.description,
|
|
246
|
-
};
|
|
247
|
-
}
|
|
248
|
-
catch {
|
|
249
|
-
return { name: productId };
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
async function fetchOpenIssues(productId) {
|
|
253
|
-
try {
|
|
254
|
-
const result = (await callMcpEndpoint('issues/list', {
|
|
255
|
-
product_id: productId,
|
|
256
|
-
}));
|
|
257
|
-
const all = result.issues || [];
|
|
258
|
-
// Dedup is only useful against issues that are still actionable.
|
|
259
|
-
return all.filter((i) => !isTerminalStatus(i.status));
|
|
260
|
-
}
|
|
261
|
-
catch (error) {
|
|
262
|
-
logWarning(`Could not load existing issues for dedup: ${error instanceof Error ? error.message : String(error)}`);
|
|
263
|
-
return [];
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
function isTerminalStatus(status) {
|
|
267
|
-
return (status === 'shipped' ||
|
|
268
|
-
status === 'archived' ||
|
|
269
|
-
status === 'cancelled' ||
|
|
270
|
-
status === 'closed' ||
|
|
271
|
-
status === 'completed');
|
|
272
|
-
}
|
|
273
196
|
async function createIssueForBug(productId, bug) {
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
description,
|
|
280
|
-
}));
|
|
281
|
-
return result.issue?.id || result.id || null;
|
|
282
|
-
}
|
|
283
|
-
catch (error) {
|
|
284
|
-
logError(`Failed to create issue for "${bug.title}": ${error instanceof Error ? error.message : String(error)}`);
|
|
285
|
-
return null;
|
|
286
|
-
}
|
|
197
|
+
return createIssue({
|
|
198
|
+
productId,
|
|
199
|
+
title: bug.title,
|
|
200
|
+
description: formatIssueDescription(bug),
|
|
201
|
+
});
|
|
287
202
|
}
|
|
288
203
|
function formatIssueDescription(bug) {
|
|
289
204
|
const location = bug.line ? `${bug.file}:${bug.line}` : bug.file;
|
|
@@ -1,44 +1,19 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Per-product scan state
|
|
2
|
+
* Per-product scan state for find-bugs. Storage at
|
|
3
|
+
* `~/.edsger/find-bugs-state/<productId>.json`.
|
|
3
4
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* 1. **Incremental scope**: `lastScannedCommitSha` tells the next run where to
|
|
7
|
-
* diff from. Only updated on a successful audit.
|
|
8
|
-
* 2. **Failure tracking**: `lastAttemptedAt` / `lastError` are updated even on
|
|
9
|
-
* failure so an orchestrating agent can back off instead of looping on a
|
|
10
|
-
* permanent misconfiguration (e.g. missing GitHub install).
|
|
11
|
-
* 3. **Concurrency**: a lock file next to the state file gates simultaneous
|
|
12
|
-
* runs for the same product — they share one workspace clone.
|
|
13
|
-
*
|
|
14
|
-
* Known limitation: state is machine-local. A scan started on machine A and
|
|
15
|
-
* next on machine B will look like a first run. Tracked for a future migration
|
|
16
|
-
* to a DB-backed store (see the v0.2 followups).
|
|
5
|
+
* Schema is bug-scan-specific (incremental commit sha tracking + failure
|
|
6
|
+
* recording); the file/lock machinery is shared via createScanStateModule.
|
|
17
7
|
*/
|
|
8
|
+
import { type LockHandle } from '../find-shared/scan-state.js';
|
|
9
|
+
export type { LockHandle };
|
|
18
10
|
export interface FindBugsState {
|
|
19
11
|
lastScannedCommitSha?: string;
|
|
20
12
|
lastScannedAt?: string;
|
|
21
13
|
lastAttemptedAt?: string;
|
|
22
14
|
lastError?: string;
|
|
23
15
|
}
|
|
24
|
-
export declare
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
*/
|
|
29
|
-
export declare function saveFindBugsState(productId: string, state: FindBugsState): void;
|
|
30
|
-
/**
|
|
31
|
-
* Merge new fields into the stored state. Fields not mentioned are preserved.
|
|
32
|
-
*/
|
|
33
|
-
export declare function updateFindBugsState(productId: string, patch: Partial<FindBugsState>): FindBugsState;
|
|
34
|
-
export interface LockHandle {
|
|
35
|
-
release: () => void;
|
|
36
|
-
}
|
|
37
|
-
/**
|
|
38
|
-
* Acquire an exclusive lock for this product's scan state.
|
|
39
|
-
*
|
|
40
|
-
* Uses O_EXCL on a lockfile, which is atomic on local POSIX filesystems (good
|
|
41
|
-
* enough — the workspace clone is also machine-local). Returns null if already
|
|
42
|
-
* held. Stale locks older than `staleAfterMs` are reclaimed.
|
|
43
|
-
*/
|
|
44
|
-
export declare function acquireFindBugsLock(productId: string, staleAfterMs?: number): LockHandle | null;
|
|
16
|
+
export declare const loadFindBugsState: (productId: string) => FindBugsState;
|
|
17
|
+
export declare const saveFindBugsState: (productId: string, state: FindBugsState) => void;
|
|
18
|
+
export declare const updateFindBugsState: (productId: string, patch: Partial<FindBugsState>) => FindBugsState;
|
|
19
|
+
export declare const acquireFindBugsLock: (productId: string, staleAfterMs?: number) => LockHandle | null;
|
|
@@ -1,121 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Per-product scan state
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
* Known limitation: state is machine-local. A scan started on machine A and
|
|
15
|
-
* next on machine B will look like a first run. Tracked for a future migration
|
|
16
|
-
* to a DB-backed store (see the v0.2 followups).
|
|
17
|
-
*/
|
|
18
|
-
import { existsSync, mkdirSync, openSync, readFileSync, renameSync, rmSync, unlinkSync, writeFileSync, } from 'fs';
|
|
19
|
-
import { homedir } from 'os';
|
|
20
|
-
import { join } from 'path';
|
|
21
|
-
function stateDir() {
|
|
22
|
-
return join(homedir(), '.edsger', 'find-bugs-state');
|
|
23
|
-
}
|
|
24
|
-
function safeProductId(productId) {
|
|
25
|
-
// productId is a UUID controlled upstream; still strip any slashes defensively.
|
|
26
|
-
return productId.replace(/[^a-zA-Z0-9_-]/g, '_');
|
|
27
|
-
}
|
|
28
|
-
function statePath(productId) {
|
|
29
|
-
return join(stateDir(), `${safeProductId(productId)}.json`);
|
|
30
|
-
}
|
|
31
|
-
function lockPath(productId) {
|
|
32
|
-
return join(stateDir(), `${safeProductId(productId)}.lock`);
|
|
33
|
-
}
|
|
34
|
-
function ensureStateDir() {
|
|
35
|
-
const dir = stateDir();
|
|
36
|
-
if (!existsSync(dir)) {
|
|
37
|
-
mkdirSync(dir, { recursive: true });
|
|
38
|
-
}
|
|
39
|
-
return dir;
|
|
40
|
-
}
|
|
41
|
-
export function loadFindBugsState(productId) {
|
|
42
|
-
const p = statePath(productId);
|
|
43
|
-
if (!existsSync(p)) {
|
|
44
|
-
return {};
|
|
45
|
-
}
|
|
46
|
-
try {
|
|
47
|
-
const raw = readFileSync(p, 'utf-8');
|
|
48
|
-
const parsed = JSON.parse(raw);
|
|
49
|
-
return parsed && typeof parsed === 'object' ? parsed : {};
|
|
50
|
-
}
|
|
51
|
-
catch {
|
|
52
|
-
return {};
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
/**
|
|
56
|
-
* Atomic write: stage to a tempfile, then rename. A crash mid-write leaves the
|
|
57
|
-
* previous state intact instead of truncating it.
|
|
58
|
-
*/
|
|
59
|
-
export function saveFindBugsState(productId, state) {
|
|
60
|
-
ensureStateDir();
|
|
61
|
-
const finalPath = statePath(productId);
|
|
62
|
-
const tmpPath = `${finalPath}.${process.pid}.tmp`;
|
|
63
|
-
writeFileSync(tmpPath, JSON.stringify(state, null, 2), 'utf-8');
|
|
64
|
-
renameSync(tmpPath, finalPath);
|
|
65
|
-
}
|
|
66
|
-
/**
|
|
67
|
-
* Merge new fields into the stored state. Fields not mentioned are preserved.
|
|
68
|
-
*/
|
|
69
|
-
export function updateFindBugsState(productId, patch) {
|
|
70
|
-
const merged = { ...loadFindBugsState(productId), ...patch };
|
|
71
|
-
saveFindBugsState(productId, merged);
|
|
72
|
-
return merged;
|
|
73
|
-
}
|
|
74
|
-
/**
|
|
75
|
-
* Acquire an exclusive lock for this product's scan state.
|
|
76
|
-
*
|
|
77
|
-
* Uses O_EXCL on a lockfile, which is atomic on local POSIX filesystems (good
|
|
78
|
-
* enough — the workspace clone is also machine-local). Returns null if already
|
|
79
|
-
* held. Stale locks older than `staleAfterMs` are reclaimed.
|
|
80
|
-
*/
|
|
81
|
-
export function acquireFindBugsLock(productId, staleAfterMs = 60 * 60 * 1000) {
|
|
82
|
-
ensureStateDir();
|
|
83
|
-
const lock = lockPath(productId);
|
|
84
|
-
if (existsSync(lock)) {
|
|
85
|
-
try {
|
|
86
|
-
const raw = readFileSync(lock, 'utf-8');
|
|
87
|
-
const parsed = JSON.parse(raw);
|
|
88
|
-
const age = Date.now() - new Date(parsed.acquiredAt ?? 0).getTime();
|
|
89
|
-
if (age < staleAfterMs) {
|
|
90
|
-
return null;
|
|
91
|
-
}
|
|
92
|
-
// Stale — reclaim. A run crashing without cleanup (SIGKILL, OOM) is the
|
|
93
|
-
// only way to reach here. Logging is the caller's job.
|
|
94
|
-
rmSync(lock, { force: true });
|
|
95
|
-
}
|
|
96
|
-
catch {
|
|
97
|
-
// Corrupt lockfile — also reclaim.
|
|
98
|
-
rmSync(lock, { force: true });
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
try {
|
|
102
|
-
// wx = fail if exists, create otherwise. Atomic against a racing peer.
|
|
103
|
-
const fd = openSync(lock, 'wx');
|
|
104
|
-
writeFileSync(fd, JSON.stringify({ acquiredAt: new Date().toISOString(), pid: process.pid }));
|
|
105
|
-
// Note: we don't hold the fd; the lock is the file's existence.
|
|
106
|
-
// Close is implicit via the writeFileSync result on this fd.
|
|
107
|
-
return {
|
|
108
|
-
release: () => {
|
|
109
|
-
try {
|
|
110
|
-
unlinkSync(lock);
|
|
111
|
-
}
|
|
112
|
-
catch {
|
|
113
|
-
// Already gone — fine.
|
|
114
|
-
}
|
|
115
|
-
},
|
|
116
|
-
};
|
|
117
|
-
}
|
|
118
|
-
catch {
|
|
119
|
-
return null;
|
|
120
|
-
}
|
|
121
|
-
}
|
|
2
|
+
* Per-product scan state for find-bugs. Storage at
|
|
3
|
+
* `~/.edsger/find-bugs-state/<productId>.json`.
|
|
4
|
+
*
|
|
5
|
+
* Schema is bug-scan-specific (incremental commit sha tracking + failure
|
|
6
|
+
* recording); the file/lock machinery is shared via createScanStateModule.
|
|
7
|
+
*/
|
|
8
|
+
import { createScanStateModule, } from '../find-shared/scan-state.js';
|
|
9
|
+
const m = createScanStateModule({ dirName: 'find-bugs-state' });
|
|
10
|
+
export const loadFindBugsState = m.load;
|
|
11
|
+
export const saveFindBugsState = m.save;
|
|
12
|
+
export const updateFindBugsState = m.update;
|
|
13
|
+
export const acquireFindBugsLock = m.acquireLock;
|
|
@@ -13,11 +13,12 @@
|
|
|
13
13
|
* code, we care what exists there.
|
|
14
14
|
*/
|
|
15
15
|
import { query } from '@anthropic-ai/claude-agent-sdk';
|
|
16
|
-
import { execFileSync } from 'child_process';
|
|
17
16
|
import { callMcpEndpoint } from '../../api/mcp-client.js';
|
|
18
17
|
import { DEFAULT_MODEL } from '../../constants.js';
|
|
19
18
|
import { logError, logInfo, logSuccess, logWarning, } from '../../utils/logger.js';
|
|
20
19
|
import { cloneIssueRepo, ensureWorkspaceDir, syncRepoToRef, } from '../../workspace/workspace-manager.js';
|
|
20
|
+
import { detectDefaultBranch } from '../find-shared/git.js';
|
|
21
|
+
import { createIssue, fetchOpenIssues, fetchProductBasics, } from '../find-shared/mcp.js';
|
|
21
22
|
import { createPromptGenerator, extractTextFromContent, tryExtractResult, } from '../pr-shared/agent-utils.js';
|
|
22
23
|
import { createFindFeaturesSystemPrompt, createFindFeaturesUserPrompt, } from './prompts.js';
|
|
23
24
|
import { acquireFindFeaturesLock, updateFindFeaturesState } from './state.js';
|
|
@@ -61,7 +62,6 @@ export async function scanForFeatures(options) {
|
|
|
61
62
|
const branch = options.branch ?? detectDefaultBranch(repoPath);
|
|
62
63
|
logInfo(`Syncing ${owner}/${repo} to branch ${branch}`);
|
|
63
64
|
syncRepoToRef(repoPath, { branch }, githubToken);
|
|
64
|
-
const headSha = gitRevParse(repoPath, 'HEAD');
|
|
65
65
|
const sinceIso = resolveSinceIso(since);
|
|
66
66
|
const product = await fetchProductBasics(productId);
|
|
67
67
|
const [feedbacks, intelligenceReports, existingIssues] = await Promise.all([
|
|
@@ -70,21 +70,11 @@ export async function scanForFeatures(options) {
|
|
|
70
70
|
fetchOpenIssues(productId),
|
|
71
71
|
]);
|
|
72
72
|
logInfo(`Loaded ${feedbacks.length} feedbacks (since ${since}), ${intelligenceReports.length} reports, ${existingIssues.length} open issues`);
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
lastRunAt: new Date().toISOString(),
|
|
77
|
-
lastScannedCommitSha: headSha,
|
|
78
|
-
lastError: undefined,
|
|
79
|
-
});
|
|
80
|
-
return {
|
|
81
|
-
status: 'success',
|
|
82
|
-
message: 'No feedback or intelligence reports available',
|
|
83
|
-
opportunitiesFound: 0,
|
|
84
|
-
issuesCreated: 0,
|
|
85
|
-
};
|
|
73
|
+
const codeOnlyMode = feedbacks.length === 0 && intelligenceReports.length === 0;
|
|
74
|
+
if (codeOnlyMode) {
|
|
75
|
+
logInfo('No feedback or intelligence reports in scope — running code-only analysis on the repo.');
|
|
86
76
|
}
|
|
87
|
-
const systemPrompt = createFindFeaturesSystemPrompt();
|
|
77
|
+
const systemPrompt = createFindFeaturesSystemPrompt({ codeOnlyMode });
|
|
88
78
|
const userPrompt = createFindFeaturesUserPrompt({
|
|
89
79
|
productName: product.name,
|
|
90
80
|
productDescription: product.description,
|
|
@@ -99,6 +89,7 @@ export async function scanForFeatures(options) {
|
|
|
99
89
|
maxSuggestions,
|
|
100
90
|
sinceWindow: since,
|
|
101
91
|
focusReportId,
|
|
92
|
+
codeOnlyMode,
|
|
102
93
|
});
|
|
103
94
|
let lastAssistantResponse = '';
|
|
104
95
|
let discoveryResult = null;
|
|
@@ -150,11 +141,12 @@ export async function scanForFeatures(options) {
|
|
|
150
141
|
logSuccess(`Filed issue ${issueId}: ${opp.title}`);
|
|
151
142
|
}
|
|
152
143
|
}
|
|
144
|
+
// The previous schema captured `lastSeenFeedbackId / lastSeenReportId /
|
|
145
|
+
// lastScannedCommitSha` here, but the reader side never used them — kept
|
|
146
|
+
// it lean and only persist the run-status fields the worker actually
|
|
147
|
+
// reads on retry. See find-features/state.ts for the schema rationale.
|
|
153
148
|
updateFindFeaturesState(productId, {
|
|
154
149
|
lastRunAt: new Date().toISOString(),
|
|
155
|
-
lastSeenFeedbackId: feedbacks[0]?.id,
|
|
156
|
-
lastSeenReportId: intelligenceReports[0]?.id,
|
|
157
|
-
lastScannedCommitSha: headSha,
|
|
158
150
|
lastError: undefined,
|
|
159
151
|
});
|
|
160
152
|
return {
|
|
@@ -178,21 +170,6 @@ export async function scanForFeatures(options) {
|
|
|
178
170
|
lock.release();
|
|
179
171
|
}
|
|
180
172
|
}
|
|
181
|
-
function gitRevParse(repoPath, ref) {
|
|
182
|
-
return execFileSync('git', ['rev-parse', ref], {
|
|
183
|
-
cwd: repoPath,
|
|
184
|
-
encoding: 'utf-8',
|
|
185
|
-
}).trim();
|
|
186
|
-
}
|
|
187
|
-
function detectDefaultBranch(repoPath) {
|
|
188
|
-
try {
|
|
189
|
-
const ref = execFileSync('git', ['symbolic-ref', '--short', 'refs/remotes/origin/HEAD'], { cwd: repoPath, encoding: 'utf-8' }).trim();
|
|
190
|
-
return ref.replace(/^origin\//, '');
|
|
191
|
-
}
|
|
192
|
-
catch {
|
|
193
|
-
return 'main';
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
173
|
/**
|
|
197
174
|
* Convert a user-friendly "since" value (e.g. "30d", "12h", ISO timestamp) to
|
|
198
175
|
* an ISO string usable as a lower bound. Falls back to 90 days ago on parse
|
|
@@ -275,56 +252,12 @@ async function fetchIntelligenceReports(productId, focusReportId) {
|
|
|
275
252
|
return [];
|
|
276
253
|
}
|
|
277
254
|
}
|
|
278
|
-
async function fetchProductBasics(productId) {
|
|
279
|
-
try {
|
|
280
|
-
const result = (await callMcpEndpoint('resources/read', {
|
|
281
|
-
uri: `product://${productId}`,
|
|
282
|
-
}));
|
|
283
|
-
const text = result.contents?.[0]?.text || '{}';
|
|
284
|
-
const parsed = JSON.parse(text);
|
|
285
|
-
return {
|
|
286
|
-
name: parsed.name || productId,
|
|
287
|
-
description: parsed.description,
|
|
288
|
-
};
|
|
289
|
-
}
|
|
290
|
-
catch {
|
|
291
|
-
return { name: productId };
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
|
-
async function fetchOpenIssues(productId) {
|
|
295
|
-
try {
|
|
296
|
-
const result = (await callMcpEndpoint('issues/list', {
|
|
297
|
-
product_id: productId,
|
|
298
|
-
}));
|
|
299
|
-
const all = result.issues || [];
|
|
300
|
-
return all.filter((i) => !isTerminalStatus(i.status));
|
|
301
|
-
}
|
|
302
|
-
catch (error) {
|
|
303
|
-
logWarning(`Could not load existing issues for dedup: ${error instanceof Error ? error.message : String(error)}`);
|
|
304
|
-
return [];
|
|
305
|
-
}
|
|
306
|
-
}
|
|
307
|
-
function isTerminalStatus(status) {
|
|
308
|
-
return (status === 'shipped' ||
|
|
309
|
-
status === 'archived' ||
|
|
310
|
-
status === 'cancelled' ||
|
|
311
|
-
status === 'closed' ||
|
|
312
|
-
status === 'completed');
|
|
313
|
-
}
|
|
314
255
|
async function createIssueForOpportunity(productId, opp) {
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
description,
|
|
321
|
-
}));
|
|
322
|
-
return result.issue?.id || result.id || null;
|
|
323
|
-
}
|
|
324
|
-
catch (error) {
|
|
325
|
-
logError(`Failed to create issue for "${opp.title}": ${error instanceof Error ? error.message : String(error)}`);
|
|
326
|
-
return null;
|
|
327
|
-
}
|
|
256
|
+
return createIssue({
|
|
257
|
+
productId,
|
|
258
|
+
title: opp.title,
|
|
259
|
+
description: formatIssueDescription(opp),
|
|
260
|
+
});
|
|
328
261
|
}
|
|
329
262
|
function formatIssueDescription(opp) {
|
|
330
263
|
const evidenceLine = opp.supporting_evidence_ids.length > 0
|
|
@@ -6,7 +6,11 @@
|
|
|
6
6
|
* "opportunities" that are really just discoverability issues on existing
|
|
7
7
|
* functionality.
|
|
8
8
|
*/
|
|
9
|
-
export
|
|
9
|
+
export interface FindFeaturesSystemPromptParams {
|
|
10
|
+
/** True when no feedback/reports were available; agent must derive opportunities purely from code. */
|
|
11
|
+
codeOnlyMode?: boolean;
|
|
12
|
+
}
|
|
13
|
+
export declare function createFindFeaturesSystemPrompt(params?: FindFeaturesSystemPromptParams): string;
|
|
10
14
|
export interface FindFeaturesUserPromptParams {
|
|
11
15
|
productName: string;
|
|
12
16
|
productDescription?: string;
|
|
@@ -33,5 +37,7 @@ export interface FindFeaturesUserPromptParams {
|
|
|
33
37
|
sinceWindow: string;
|
|
34
38
|
/** Optional narrowing to a specific intelligence report's context. */
|
|
35
39
|
focusReportId?: string;
|
|
40
|
+
/** True when the run has no feedback/reports and must analyse the codebase only. */
|
|
41
|
+
codeOnlyMode?: boolean;
|
|
36
42
|
}
|
|
37
43
|
export declare function createFindFeaturesUserPrompt(params: FindFeaturesUserPromptParams): string;
|