@openpome/local-gateway 0.38.0-alpha.0 → 0.41.0-alpha.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/dist/index.js CHANGED
@@ -16,6 +16,7 @@ import { createDefaultWorkItemSourceRegistry, createJiraCloudOAuthLogin, exchang
16
16
  const execAsync = promisify(exec);
17
17
  const execFileAsync = promisify(execFile);
18
18
  const jiraOAuthCredentialAccount = "jira-cloud/oauth";
19
+ const jiraApiTokenCredentialAccount = "jira-cloud/api-token";
19
20
  const githubOAuthCredentialAccount = "github/oauth";
20
21
  const openAiCredentialAccount = "model/openai/api-key";
21
22
  const anthropicCredentialAccount = "model/anthropic/api-key";
@@ -41,7 +42,7 @@ const maxWorkspaceScanRepositories = 200;
41
42
  export function getGatewayHealth() {
42
43
  return {
43
44
  status: "ok",
44
- version: "0.38.0-alpha.0"
45
+ version: "0.41.0-alpha.0"
45
46
  };
46
47
  }
47
48
  export async function initOpenPome() {
@@ -453,11 +454,17 @@ export async function startTaskSession(key, env = process.env) {
453
454
  updatedAt: now
454
455
  };
455
456
  const events = createSessionStartEvents(session, resolution.workItem, workspaceCandidate, now);
457
+ const repositoryKnowledge = workspaceCandidate?.workspace.path
458
+ ? await buildRepositoryKnowledge(workspaceCandidate.workspace, now)
459
+ : undefined;
460
+ const intelligence = buildWorkItemIntelligenceReport(resolution.workItem, workspaceCandidate, repositoryKnowledge);
456
461
  await writeActiveTaskSession(paths.homeDirectory, {
457
462
  version: 1,
458
463
  session,
459
464
  workItem: resolution.workItem,
460
465
  workspaceCandidate,
466
+ intelligence,
467
+ repositoryKnowledge,
461
468
  events,
462
469
  approvalHistory: []
463
470
  });
@@ -465,6 +472,8 @@ export async function startTaskSession(key, env = process.env) {
465
472
  session,
466
473
  workItem: resolution.workItem,
467
474
  workspaceCandidate,
475
+ intelligence,
476
+ repositoryKnowledge,
468
477
  sessionFile: getActiveTaskSessionFile(paths.homeDirectory)
469
478
  };
470
479
  }
@@ -483,6 +492,8 @@ export async function getTaskSessionStatus() {
483
492
  session: persisted.session,
484
493
  workItem: persisted.workItem,
485
494
  workspaceCandidate: persisted.workspaceCandidate,
495
+ intelligence: persisted.intelligence,
496
+ repositoryKnowledge: persisted.repositoryKnowledge,
486
497
  plan: persisted.plan,
487
498
  planApproval: persisted.planApproval,
488
499
  events: persisted.events ?? [],
@@ -501,12 +512,12 @@ export async function getTaskSessionStatus() {
501
512
  }
502
513
  export async function getAssistantDecision() {
503
514
  const status = await getTaskSessionStatus();
515
+ const jira = await getJiraAuthStatus();
504
516
  if (!status.active || !status.session || !status.workItem) {
505
- const jira = await getJiraAuthStatus();
506
517
  if (!jira.configured) {
507
518
  return buildAssistantDecision(status, "connect_jira", "Connect Jira", "OpenPome needs Jira access before it can show assigned stories.", [
508
519
  "pome onboard",
509
- "pome auth jira login --listen",
520
+ "pome auth jira token",
510
521
  "pome demo"
511
522
  ], [jira.detail]);
512
523
  }
@@ -515,6 +526,16 @@ export async function getAssistantDecision() {
515
526
  "pome start <KEY>"
516
527
  ]);
517
528
  }
529
+ if (!jira.configured && process.env["OPENPOME_DEMO"] !== "1") {
530
+ return buildAssistantDecision(status, "connect_jira", "Connect Jira", "OpenPome needs Jira connected before continuing this story as real work.", [
531
+ "pome auth jira token",
532
+ "pome reset",
533
+ "pome demo"
534
+ ], [
535
+ jira.detail,
536
+ "If this is an old demo/session, run `pome reset` and start from `pome work` after connecting Jira."
537
+ ]);
538
+ }
518
539
  if (!status.plan) {
519
540
  return buildAssistantDecision(status, "create_plan", "Create implementation plan", "Build a repo-aware implementation plan from the latest Jira story.", [
520
541
  "pome plan"
@@ -1653,6 +1674,49 @@ export async function getJiraAuthStatus(env = process.env) {
1653
1674
  ...status
1654
1675
  };
1655
1676
  }
1677
+ export async function configureJiraApiTokenAuth(input, env = process.env) {
1678
+ const baseUrl = normalizeJiraBaseUrl(input.baseUrl);
1679
+ const email = input.email.trim();
1680
+ const apiToken = input.apiToken.trim();
1681
+ if (!baseUrl) {
1682
+ throw new Error("Jira site URL is required. Example: https://your-company.atlassian.net");
1683
+ }
1684
+ if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/u.test(email)) {
1685
+ throw new Error("Jira email is required. Use the email address you use to sign in to Atlassian.");
1686
+ }
1687
+ if (!apiToken) {
1688
+ throw new Error("Jira API token is required.");
1689
+ }
1690
+ const store = createCredentialStore();
1691
+ if (!store.isAvailable()) {
1692
+ throw new Error(`Credential store is unavailable: ${store.backend}. Use OPENPOME_JIRA_BASE_URL, OPENPOME_JIRA_EMAIL, and OPENPOME_JIRA_API_TOKEN environment variables instead.`);
1693
+ }
1694
+ const credential = {
1695
+ baseUrl,
1696
+ email,
1697
+ apiToken,
1698
+ storedAt: new Date().toISOString()
1699
+ };
1700
+ await setJsonCredential(store, jiraApiTokenCredentialAccount, credential);
1701
+ const source = await createJiraSource({
1702
+ ...env,
1703
+ OPENPOME_JIRA_BASE_URL: baseUrl,
1704
+ OPENPOME_JIRA_EMAIL: email,
1705
+ OPENPOME_JIRA_API_TOKEN: apiToken
1706
+ });
1707
+ const reachability = await source.checkReachability();
1708
+ if (reachability.status !== "ok" && reachability.status !== "reachable") {
1709
+ throw new Error(`Jira credentials were saved, but Jira was not reachable yet. ${reachability.detail}`);
1710
+ }
1711
+ return {
1712
+ provider: "jira-cloud",
1713
+ stored: true,
1714
+ mode: "api-token",
1715
+ baseUrl,
1716
+ email,
1717
+ detail: "Jira connected. OpenPome can now load your assigned stories."
1718
+ };
1719
+ }
1656
1720
  export function createJiraOAuthLogin(env = process.env) {
1657
1721
  const clientId = env["OPENPOME_JIRA_OAUTH_CLIENT_ID"];
1658
1722
  const redirectUri = env["OPENPOME_JIRA_OAUTH_REDIRECT_URI"] ?? "http://127.0.0.1:48731/auth/jira/callback";
@@ -1764,10 +1828,15 @@ async function createJiraSource(env) {
1764
1828
  const paths = getOpenPomePaths();
1765
1829
  const localConfig = await readConfigIfPresent(paths.configFile);
1766
1830
  const selectedBoardScope = getActiveJiraBoardScope(localConfig);
1831
+ const storedApiToken = await readStoredJiraApiToken();
1767
1832
  const storedOAuth = await refreshStoredJiraOAuthIfNeeded(await readStoredJiraOAuth(), env);
1833
+ const connectorCredentials = {
1834
+ ...(storedApiToken ? { [jiraApiTokenCredentialAccount]: storedApiToken } : {}),
1835
+ ...(storedOAuth ? { [jiraOAuthCredentialAccount]: storedOAuth } : {})
1836
+ };
1768
1837
  return workItemSourceRegistry.getActiveSource(env, {
1769
1838
  activeScope: selectedBoardScope,
1770
- connectorCredentials: storedOAuth ? { [jiraOAuthCredentialAccount]: storedOAuth } : undefined
1839
+ connectorCredentials: Object.keys(connectorCredentials).length ? connectorCredentials : undefined
1771
1840
  });
1772
1841
  }
1773
1842
  function normalizeModelProviderId(provider) {
@@ -1867,6 +1936,13 @@ async function readStoredJiraOAuth() {
1867
1936
  }
1868
1937
  return getJsonCredential(store, jiraOAuthCredentialAccount);
1869
1938
  }
1939
+ async function readStoredJiraApiToken() {
1940
+ const store = createCredentialStore();
1941
+ if (!store.isAvailable()) {
1942
+ return undefined;
1943
+ }
1944
+ return getJsonCredential(store, jiraApiTokenCredentialAccount);
1945
+ }
1870
1946
  async function readStoredGitHubOAuth() {
1871
1947
  const store = createCredentialStore();
1872
1948
  if (!store.isAvailable()) {
@@ -2064,6 +2140,13 @@ function getOpenPomePaths() {
2064
2140
  configFile: join(homeDirectory, "config.json")
2065
2141
  };
2066
2142
  }
2143
+ function normalizeJiraBaseUrl(value) {
2144
+ const trimmed = value.trim().replace(/\/+$/u, "");
2145
+ if (!trimmed) {
2146
+ return "";
2147
+ }
2148
+ return /^https?:\/\//iu.test(trimmed) ? trimmed : `https://${trimmed}`;
2149
+ }
2067
2150
  async function readConfigIfPresent(configFile) {
2068
2151
  try {
2069
2152
  const content = await readFile(configFile, "utf8");
@@ -2285,6 +2368,255 @@ async function readPackageScripts(packageFile) {
2285
2368
  throw error;
2286
2369
  }
2287
2370
  }
2371
+ async function buildRepositoryKnowledge(workspace, now) {
2372
+ if (!workspace.path) {
2373
+ return undefined;
2374
+ }
2375
+ const workspacePath = workspace.path;
2376
+ const trackedFiles = await listWorkspaceFilesForKnowledge(workspacePath);
2377
+ const packageManager = detectPackageManager(workspacePath);
2378
+ const manifests = trackedFiles
2379
+ .filter((filePath) => basename(filePath) === "package.json")
2380
+ .filter((filePath) => !isGeneratedWorkspacePath(filePath) && !isSensitiveWorkspacePath(filePath))
2381
+ .sort((left, right) => left.localeCompare(right))
2382
+ .slice(0, 80);
2383
+ const rootScripts = await readPackageScripts(join(workspacePath, "package.json"));
2384
+ const packageNames = await readPackageNamesFromManifests(workspacePath, manifests);
2385
+ const codeowners = await readRepositoryCodeowners(workspacePath);
2386
+ const knowledgeFile = getRepositoryKnowledgeFile(workspacePath);
2387
+ const pathMap = buildRepositoryPathMap(trackedFiles);
2388
+ const knowledge = {
2389
+ schemaVersion: 1,
2390
+ createdAt: await readExistingRepositoryKnowledgeCreatedAt(knowledgeFile, now),
2391
+ updatedAt: now,
2392
+ workspace: {
2393
+ name: workspace.name,
2394
+ path: workspacePath,
2395
+ remoteUrls: workspace.remoteUrls,
2396
+ currentBranch: workspace.currentBranch
2397
+ },
2398
+ packageMap: {
2399
+ packageManager,
2400
+ packageNames: packageNames.length ? packageNames : workspace.packageNames ?? [],
2401
+ manifests,
2402
+ scripts: await buildRepositoryScriptMap(workspacePath, manifests, rootScripts),
2403
+ buildCommands: buildRepositoryCommands(packageManager, rootScripts, ["build", "compile"]),
2404
+ testCommands: buildRepositoryCommands(packageManager, rootScripts, ["test", "test:unit", "test:integration", "test:e2e"]),
2405
+ lintCommands: buildRepositoryCommands(packageManager, rootScripts, ["lint", "lint:fix"]),
2406
+ typecheckCommands: buildRepositoryCommands(packageManager, rootScripts, ["typecheck", "tsc"]),
2407
+ validateCommands: buildRepositoryCommands(packageManager, rootScripts, ["validate", "check", "ci"])
2408
+ },
2409
+ pathMap,
2410
+ moduleBoundaries: buildRepositoryModuleBoundaries(trackedFiles, packageNames),
2411
+ ownership: {
2412
+ codeownersFiles: codeowners.codeownersFiles,
2413
+ owners: codeowners.owners,
2414
+ signals: codeowners.signals
2415
+ },
2416
+ knowledgeFile
2417
+ };
2418
+ await mkdir(dirname(knowledgeFile), { recursive: true });
2419
+ await writeFile(knowledgeFile, `${JSON.stringify(knowledge, null, 2)}\n`, "utf8");
2420
+ return knowledge;
2421
+ }
2422
+ async function readExistingRepositoryKnowledgeCreatedAt(knowledgeFile, fallback) {
2423
+ try {
2424
+ const content = await readFile(knowledgeFile, "utf8");
2425
+ const parsed = JSON.parse(content);
2426
+ return typeof parsed.createdAt === "string" ? parsed.createdAt : fallback;
2427
+ }
2428
+ catch (error) {
2429
+ if (error instanceof Error && "code" in error && error.code === "ENOENT") {
2430
+ return fallback;
2431
+ }
2432
+ if (error instanceof SyntaxError) {
2433
+ return fallback;
2434
+ }
2435
+ throw error;
2436
+ }
2437
+ }
2438
+ function getRepositoryKnowledgeFile(workspacePath) {
2439
+ return join(workspacePath, ".pome", "knowledge", "repository.json");
2440
+ }
2441
+ async function readPackageNamesFromManifests(workspacePath, manifests) {
2442
+ const names = [];
2443
+ for (const manifest of manifests.slice(0, 80)) {
2444
+ const name = await readPackageName(join(workspacePath, manifest));
2445
+ if (name) {
2446
+ names.push(name);
2447
+ }
2448
+ }
2449
+ return uniqueStrings(names).slice(0, 80);
2450
+ }
2451
+ async function buildRepositoryScriptMap(workspacePath, manifests, rootScripts) {
2452
+ const scripts = { ...rootScripts };
2453
+ for (const manifest of manifests.filter((filePath) => filePath !== "package.json").slice(0, 40)) {
2454
+ const packageName = await readPackageName(join(workspacePath, manifest));
2455
+ const packageScripts = await readPackageScripts(join(workspacePath, manifest));
2456
+ const prefix = packageName ?? dirname(manifest);
2457
+ for (const [scriptName, script] of Object.entries(packageScripts)) {
2458
+ scripts[`${prefix}:${scriptName}`] = script;
2459
+ }
2460
+ }
2461
+ return Object.fromEntries(Object.entries(scripts).slice(0, 120));
2462
+ }
2463
+ function buildRepositoryCommands(packageManager, scripts, preferredScriptNames) {
2464
+ return preferredScriptNames
2465
+ .filter((scriptName) => Boolean(scripts[scriptName]))
2466
+ .map((scriptName) => buildPackageScriptCommand(packageManager, scriptName))
2467
+ .slice(0, 8);
2468
+ }
2469
+ function buildRepositoryPathMap(files) {
2470
+ const source = [];
2471
+ const tests = [];
2472
+ const config = [];
2473
+ const generated = [];
2474
+ const sensitive = [];
2475
+ const docs = [];
2476
+ for (const filePath of files.slice(0, 2000)) {
2477
+ const normalized = filePath.replace(/\\/gu, "/");
2478
+ if (isSensitiveWorkspacePath(normalized)) {
2479
+ sensitive.push(normalized);
2480
+ continue;
2481
+ }
2482
+ if (isGeneratedWorkspacePath(normalized)) {
2483
+ generated.push(normalized);
2484
+ continue;
2485
+ }
2486
+ if (isDocumentationWorkspacePath(normalized)) {
2487
+ docs.push(normalized);
2488
+ }
2489
+ if (isConfigWorkspacePath(normalized)) {
2490
+ config.push(normalized);
2491
+ continue;
2492
+ }
2493
+ if (isTestLikeFile(normalized)) {
2494
+ tests.push(normalized);
2495
+ continue;
2496
+ }
2497
+ if (isSourceWorkspacePath(normalized)) {
2498
+ source.push(normalized);
2499
+ }
2500
+ }
2501
+ return {
2502
+ source: uniqueStrings(source).slice(0, 160),
2503
+ tests: uniqueStrings(tests).slice(0, 120),
2504
+ config: uniqueStrings(config).slice(0, 80),
2505
+ generated: uniqueStrings(generated).slice(0, 80),
2506
+ sensitive: uniqueStrings(sensitive).slice(0, 40),
2507
+ docs: uniqueStrings(docs).slice(0, 40)
2508
+ };
2509
+ }
2510
+ function buildRepositoryModuleBoundaries(files, packageNames) {
2511
+ const boundaries = new Map();
2512
+ for (const filePath of files) {
2513
+ if (basename(filePath) === "package.json") {
2514
+ const directory = dirname(filePath);
2515
+ const normalizedDirectory = directory === "." ? "." : directory;
2516
+ const packageName = packageNames.find((name) => normalizeModuleName(name).includes(basename(normalizedDirectory).toLowerCase()));
2517
+ boundaries.set(normalizedDirectory, {
2518
+ name: packageName ?? (basename(normalizedDirectory) || "root"),
2519
+ path: normalizedDirectory,
2520
+ kind: inferModuleBoundaryKind(normalizedDirectory),
2521
+ reason: "Package manifest defines a build or runtime boundary."
2522
+ });
2523
+ continue;
2524
+ }
2525
+ const topLevel = filePath.split("/")[0] ?? "";
2526
+ if (!topLevel || topLevel.startsWith(".")) {
2527
+ continue;
2528
+ }
2529
+ if (/^(apps|services|packages|connectors|src|lib)$/u.test(topLevel)) {
2530
+ const parts = filePath.split("/");
2531
+ const boundaryPath = topLevel === "src" || topLevel === "lib" ? topLevel : parts.slice(0, 2).join("/");
2532
+ if (boundaryPath && !boundaries.has(boundaryPath)) {
2533
+ boundaries.set(boundaryPath, {
2534
+ name: basename(boundaryPath),
2535
+ path: boundaryPath,
2536
+ kind: inferModuleBoundaryKind(boundaryPath),
2537
+ reason: `Repository path suggests a ${inferModuleBoundaryKind(boundaryPath)} boundary.`
2538
+ });
2539
+ }
2540
+ }
2541
+ }
2542
+ return [...boundaries.values()]
2543
+ .sort((left, right) => left.path.localeCompare(right.path))
2544
+ .slice(0, 80);
2545
+ }
2546
+ function inferModuleBoundaryKind(path) {
2547
+ if (path.startsWith("apps/")) {
2548
+ return "application";
2549
+ }
2550
+ if (path.startsWith("services/")) {
2551
+ return "service";
2552
+ }
2553
+ if (path.startsWith("packages/")) {
2554
+ return "package";
2555
+ }
2556
+ if (path.startsWith("connectors/")) {
2557
+ return "connector";
2558
+ }
2559
+ if (path === "src" || path.startsWith("src/") || path === "lib" || path.startsWith("lib/")) {
2560
+ return "source";
2561
+ }
2562
+ return "module";
2563
+ }
2564
+ function normalizeModuleName(value) {
2565
+ return value.toLowerCase().replace(/^@[^/]+\//u, "").replace(/[^a-z0-9]+/gu, "-");
2566
+ }
2567
+ async function readRepositoryCodeowners(workspacePath) {
2568
+ const codeownersFiles = [".github/CODEOWNERS", "CODEOWNERS", "docs/CODEOWNERS"];
2569
+ const foundFiles = [];
2570
+ const owners = [];
2571
+ const signals = [];
2572
+ for (const filePath of codeownersFiles) {
2573
+ const content = await readOptionalTextFile(join(workspacePath, filePath), 32_000);
2574
+ if (!content) {
2575
+ continue;
2576
+ }
2577
+ foundFiles.push(filePath);
2578
+ for (const line of content.split(/\r?\n/u)) {
2579
+ const trimmed = line.trim();
2580
+ if (!trimmed || trimmed.startsWith("#")) {
2581
+ continue;
2582
+ }
2583
+ const parts = trimmed.split(/\s+/u);
2584
+ const pathPattern = parts[0];
2585
+ const lineOwners = parts.slice(1).filter((owner) => owner.startsWith("@") || owner.includes("@"));
2586
+ owners.push(...lineOwners);
2587
+ if (pathPattern && lineOwners.length) {
2588
+ signals.push(`${pathPattern}: ${lineOwners.slice(0, 4).join(", ")}`);
2589
+ }
2590
+ }
2591
+ }
2592
+ return {
2593
+ codeownersFiles: foundFiles,
2594
+ owners: uniqueStrings(owners).slice(0, 40),
2595
+ signals: uniqueStrings(signals).slice(0, 40)
2596
+ };
2597
+ }
2598
+ function isSourceWorkspacePath(filePath) {
2599
+ const lower = filePath.toLowerCase();
2600
+ return /\.(ts|tsx|js|jsx|mjs|cjs|py|java|kt|go|rs|rb|php|cs|swift|scala|sh|sql)$/u.test(lower)
2601
+ && !isConfigWorkspacePath(lower)
2602
+ && !isGeneratedWorkspacePath(lower);
2603
+ }
2604
+ function isConfigWorkspacePath(filePath) {
2605
+ const lower = filePath.toLowerCase();
2606
+ return /(^|\/)(package\.json|pnpm-workspace\.yaml|tsconfig[^/]*\.json|vite\.config\.[cm]?[jt]s|vitest\.config\.[cm]?[jt]s|jest\.config\.[cm]?[jt]s|eslint\.config\.[cm]?[jt]s|\.eslintrc[^/]*|\.prettierrc[^/]*|dockerfile|docker-compose\.ya?ml|makefile|cargo\.toml|go\.mod|pom\.xml|gradle\.properties|build\.gradle|pyproject\.toml|requirements\.txt|ruff\.toml)$/u.test(lower)
2607
+ || /(^|\/)(\.github\/workflows\/.+\.ya?ml|config\/.+\.(json|ya?ml|toml|ini))$/u.test(lower);
2608
+ }
2609
+ function isGeneratedWorkspacePath(filePath) {
2610
+ const lower = filePath.toLowerCase();
2611
+ return /(^|\/)(dist|build|coverage|out|target|\.next|\.nuxt|\.turbo|\.cache|vendor|node_modules|generated|__generated__)\//u.test(lower)
2612
+ || /\.(tsbuildinfo|min\.js|min\.css|map)$/u.test(lower)
2613
+ || /(^|\/)(pnpm-lock\.yaml|package-lock\.json|yarn\.lock|bun\.lockb?)$/u.test(lower);
2614
+ }
2615
+ function isDocumentationWorkspacePath(filePath) {
2616
+ const lower = filePath.toLowerCase();
2617
+ return /(^|\/)(readme|changelog|license|contributing)(\.[a-z0-9]+)?$/u.test(lower)
2618
+ || lower.startsWith("docs/");
2619
+ }
2288
2620
  async function readWorkspaceReadmeKeywords(directory) {
2289
2621
  const readmeFiles = ["README.md", "README.txt", "readme.md"];
2290
2622
  const keywords = [];
@@ -2737,6 +3069,7 @@ function selectArchivedTaskSession(sessions, sessionId) {
2737
3069
  }
2738
3070
  function buildPlanningContext(session) {
2739
3071
  const workspace = session.workspaceCandidate?.workspace;
3072
+ const knowledge = session.repositoryKnowledge;
2740
3073
  const missingRequirementSignals = detectMissingRequirementSignals(session.workItem);
2741
3074
  const context = [
2742
3075
  `Work item type: ${session.workItem.type}`,
@@ -2759,7 +3092,17 @@ function buildPlanningContext(session) {
2759
3092
  workspace?.readmeKeywords?.length ? `README signals: ${workspace.readmeKeywords.slice(0, 12).join(", ")}` : undefined,
2760
3093
  workspace?.codeownersKeywords?.length ? `Ownership signals: ${workspace.codeownersKeywords.slice(0, 12).join(", ")}` : undefined,
2761
3094
  workspace?.recentBranches?.length ? `Recent branches: ${workspace.recentBranches.slice(0, 8).join(", ")}` : undefined,
2762
- workspace?.recentCommitRefs?.length ? `Recent work refs: ${workspace.recentCommitRefs.slice(0, 12).join(", ")}` : undefined
3095
+ workspace?.recentCommitRefs?.length ? `Recent work refs: ${workspace.recentCommitRefs.slice(0, 12).join(", ")}` : undefined,
3096
+ knowledge ? `Repository knowledge file: ${knowledge.knowledgeFile}` : undefined,
3097
+ knowledge ? `Repository package manager: ${knowledge.packageMap.packageManager}` : undefined,
3098
+ knowledge?.packageMap.packageNames.length ? `Repository packages: ${knowledge.packageMap.packageNames.slice(0, 12).join(", ")}` : undefined,
3099
+ knowledge?.moduleBoundaries.length
3100
+ ? `Module boundaries: ${knowledge.moduleBoundaries.slice(0, 12).map((boundary) => `${boundary.kind}:${boundary.path}`).join(", ")}`
3101
+ : undefined,
3102
+ knowledge ? `Path map: ${knowledge.pathMap.source.length} source, ${knowledge.pathMap.tests.length} test, ${knowledge.pathMap.config.length} config, ${knowledge.pathMap.generated.length} generated, ${knowledge.pathMap.sensitive.length} sensitive paths` : undefined,
3103
+ knowledge?.ownership.owners.length ? `Code owners: ${knowledge.ownership.owners.slice(0, 12).join(", ")}` : undefined,
3104
+ knowledge?.packageMap.validateCommands.length ? `Validation commands: ${knowledge.packageMap.validateCommands.join(", ")}` : undefined,
3105
+ knowledge?.packageMap.testCommands.length ? `Test commands: ${knowledge.packageMap.testCommands.join(", ")}` : undefined
2763
3106
  ];
2764
3107
  return context.filter((item) => Boolean(item));
2765
3108
  }
@@ -2813,6 +3156,307 @@ function buildInitialImplementationPlan(workItem, workspaceCandidate) {
2813
3156
  missingInfo
2814
3157
  };
2815
3158
  }
3159
+ function buildWorkItemIntelligenceReport(workItem, workspaceCandidate, repositoryKnowledge) {
3160
+ const acceptanceCriteria = extractAcceptanceCriteria(workItem);
3161
+ const missingQuestions = detectMissingRequirementSignals(workItem);
3162
+ const linkedReferences = summarizeLinkedReferences(workItem);
3163
+ const likelyFiles = inferLikelyFileHints(workItem, workspaceCandidate, repositoryKnowledge);
3164
+ const dependencies = inferDependencySignals(workItem, workspaceCandidate, repositoryKnowledge);
3165
+ const risks = inferWorkItemRisks(workItem, workspaceCandidate, acceptanceCriteria, likelyFiles);
3166
+ return {
3167
+ summary: summarizeWorkItem(workItem),
3168
+ acceptanceCriteria,
3169
+ missingQuestions,
3170
+ affectedRepositories: workspaceCandidate
3171
+ ? [{
3172
+ name: workspaceCandidate.workspace.name,
3173
+ path: workspaceCandidate.workspace.path,
3174
+ reasons: workspaceCandidate.reasons.slice(0, 6)
3175
+ }]
3176
+ : [],
3177
+ likelyFiles,
3178
+ linkedReferences,
3179
+ dependencies,
3180
+ risks,
3181
+ testStrategy: inferTestStrategy(workItem, workspaceCandidate, likelyFiles, repositoryKnowledge),
3182
+ deliveryChecklist: buildDeliveryChecklist(workItem)
3183
+ };
3184
+ }
3185
+ function summarizeWorkItem(workItem) {
3186
+ const displayType = `${workItem.type.charAt(0).toUpperCase()}${workItem.type.slice(1)}`;
3187
+ const fragments = [
3188
+ `${displayType} ${workItem.key}`,
3189
+ `is currently ${workItem.status}`,
3190
+ workItem.priority ? `with ${workItem.priority} priority` : undefined,
3191
+ `and asks for: ${workItem.title}`
3192
+ ].filter((item) => Boolean(item));
3193
+ return `${fragments.join(" ")}.`;
3194
+ }
3195
+ function extractAcceptanceCriteria(workItem) {
3196
+ const description = workItem.description?.trim();
3197
+ if (!description) {
3198
+ return [];
3199
+ }
3200
+ const lines = description
3201
+ .split(/\r?\n/u)
3202
+ .map((line) => line.trim())
3203
+ .filter(Boolean);
3204
+ const criteria = [];
3205
+ let collecting = false;
3206
+ for (const line of lines) {
3207
+ const normalized = line.toLowerCase();
3208
+ if (/\b(acceptance criteria|success criteria|definition of done|expected result|expected behavior|validation)\b/u.test(normalized)) {
3209
+ collecting = true;
3210
+ const inline = line.split(/:\s*/u).slice(1).join(":").trim();
3211
+ if (inline) {
3212
+ criteria.push(cleanCriteriaLine(inline));
3213
+ }
3214
+ continue;
3215
+ }
3216
+ if (collecting) {
3217
+ if (/^[a-z][a-z ]{1,30}:$/u.test(normalized) && !/\b(given|when|then|should|must|verify|validate|done|expected)\b/u.test(normalized)) {
3218
+ collecting = false;
3219
+ continue;
3220
+ }
3221
+ if (/^[-*•\d.)\s]+/u.test(line) || /\b(given|when|then|should|must|verify|validate|done when|expected)\b/u.test(normalized)) {
3222
+ criteria.push(cleanCriteriaLine(line));
3223
+ }
3224
+ }
3225
+ else if (/\b(given\b.*\bwhen\b.*\bthen|should|must|verify|validate|done when|expected)\b/su.test(normalized)) {
3226
+ criteria.push(cleanCriteriaLine(line));
3227
+ }
3228
+ }
3229
+ return uniqueStrings(criteria).slice(0, 8);
3230
+ }
3231
+ function cleanCriteriaLine(line) {
3232
+ return line
3233
+ .replace(/^[-*•\d.)\s]+/u, "")
3234
+ .trim();
3235
+ }
3236
+ function summarizeLinkedReferences(workItem) {
3237
+ return (workItem.links ?? []).map((link) => ({
3238
+ kind: link.kind,
3239
+ title: link.title ?? link.url,
3240
+ url: link.url
3241
+ })).slice(0, 10);
3242
+ }
3243
+ function inferLikelyFileHints(workItem, workspaceCandidate, repositoryKnowledge) {
3244
+ const hints = [];
3245
+ const workspace = workspaceCandidate?.workspace;
3246
+ const tokens = tokenizePatchSearchText([
3247
+ workItem.key,
3248
+ workItem.title,
3249
+ workItem.description,
3250
+ ...(workItem.labels ?? []),
3251
+ ...(workItem.components ?? []),
3252
+ ...(repositoryKnowledge?.packageMap.packageNames ?? []),
3253
+ ...(repositoryKnowledge?.moduleBoundaries.map((boundary) => `${boundary.name} ${boundary.path}`) ?? [])
3254
+ ].filter((value) => Boolean(value)).join(" "));
3255
+ for (const link of workItem.links ?? []) {
3256
+ if (link.kind !== "code" && link.kind !== "pull_request") {
3257
+ continue;
3258
+ }
3259
+ const path = inferCodePathFromUrl(link.url);
3260
+ if (path) {
3261
+ hints.push({
3262
+ path,
3263
+ reason: `${link.kind === "pull_request" ? "Linked pull request" : "Linked code"} references this path.`
3264
+ });
3265
+ }
3266
+ }
3267
+ for (const component of workItem.components ?? []) {
3268
+ const slug = slugifyPathSegment(component);
3269
+ if (slug) {
3270
+ hints.push({
3271
+ path: `**/${slug}/**`,
3272
+ reason: `Jira component "${component}" should narrow the code area.`
3273
+ });
3274
+ }
3275
+ }
3276
+ for (const label of workItem.labels ?? []) {
3277
+ const slug = slugifyPathSegment(label);
3278
+ if (slug) {
3279
+ hints.push({
3280
+ path: `**/${slug}/**`,
3281
+ reason: `Jira label "${label}" may map to package, module, or test names.`
3282
+ });
3283
+ }
3284
+ }
3285
+ const title = workItem.title.toLowerCase();
3286
+ if (/\b(test|spec|validation|qa)\b/u.test(title)) {
3287
+ hints.push({ path: "**/*.{test,spec}.*", reason: "Story title mentions tests or validation." });
3288
+ }
3289
+ if (/\b(api|endpoint|controller|route)\b/u.test(title)) {
3290
+ hints.push({ path: "**/{api,routes,controllers}/**", reason: "Story title mentions API or endpoint work." });
3291
+ }
3292
+ if (/\b(ui|screen|page|component|button|form)\b/u.test(title)) {
3293
+ hints.push({ path: "**/{components,pages,app,src}/**", reason: "Story title mentions UI or component work." });
3294
+ }
3295
+ if (/\b(config|setting|env|feature flag|flag)\b/u.test(title)) {
3296
+ hints.push({ path: "**/{config,.github,scripts}/**", reason: "Story title mentions configuration or flags." });
3297
+ }
3298
+ if (workspace?.packageNames?.length) {
3299
+ for (const packageName of workspace.packageNames.slice(0, 4)) {
3300
+ hints.push({
3301
+ path: packageName,
3302
+ reason: "Workspace package metadata is relevant to this task."
3303
+ });
3304
+ }
3305
+ }
3306
+ if (repositoryKnowledge) {
3307
+ for (const boundary of rankKnowledgeModuleBoundaries(repositoryKnowledge, tokens).slice(0, 5)) {
3308
+ hints.push({
3309
+ path: boundary.path,
3310
+ reason: `Repository knowledge identifies ${boundary.kind} boundary "${boundary.name}" as relevant.`
3311
+ });
3312
+ }
3313
+ for (const filePath of rankKnowledgePaths(repositoryKnowledge, tokens).slice(0, 8)) {
3314
+ hints.push({
3315
+ path: filePath,
3316
+ reason: "Repository knowledge matched this tracked path to the story text, labels, components, or plan signals."
3317
+ });
3318
+ }
3319
+ }
3320
+ if (workspace?.path && hints.length === 0) {
3321
+ hints.push({
3322
+ path: workspace.path,
3323
+ reason: "Selected workspace is the current best code context; repository knowledge will narrow files further."
3324
+ });
3325
+ }
3326
+ return uniqueFileHints(hints).slice(0, 10);
3327
+ }
3328
+ function rankKnowledgeModuleBoundaries(repositoryKnowledge, tokens) {
3329
+ return repositoryKnowledge.moduleBoundaries
3330
+ .map((boundary) => ({
3331
+ boundary,
3332
+ score: scoreKnowledgeText(`${boundary.name} ${boundary.path} ${boundary.kind}`, tokens)
3333
+ }))
3334
+ .filter((candidate) => candidate.score > 0)
3335
+ .sort((left, right) => right.score - left.score || left.boundary.path.localeCompare(right.boundary.path))
3336
+ .map((candidate) => candidate.boundary);
3337
+ }
3338
+ function rankKnowledgePaths(repositoryKnowledge, tokens) {
3339
+ return [
3340
+ ...repositoryKnowledge.pathMap.source,
3341
+ ...repositoryKnowledge.pathMap.tests,
3342
+ ...repositoryKnowledge.pathMap.config
3343
+ ]
3344
+ .map((filePath) => ({
3345
+ filePath,
3346
+ score: scoreKnowledgeText(filePath, tokens)
3347
+ + (isTestLikeFile(filePath) ? 3 : 0)
3348
+ + (isConfigWorkspacePath(filePath) ? 1 : 0)
3349
+ }))
3350
+ .filter((candidate) => candidate.score > 0)
3351
+ .sort((left, right) => right.score - left.score || left.filePath.localeCompare(right.filePath))
3352
+ .map((candidate) => candidate.filePath);
3353
+ }
3354
+ function scoreKnowledgeText(value, tokens) {
3355
+ const lower = value.toLowerCase();
3356
+ let score = 0;
3357
+ for (const token of tokens) {
3358
+ if (token.length >= 3 && lower.includes(token)) {
3359
+ score += token.includes("/") ? 8 : 4;
3360
+ }
3361
+ }
3362
+ return score;
3363
+ }
3364
+ function inferDependencySignals(workItem, workspaceCandidate, repositoryKnowledge) {
3365
+ const workspace = workspaceCandidate?.workspace;
3366
+ const signals = [
3367
+ workItem.parentKey ? `Parent work item: ${workItem.parentKey}` : undefined,
3368
+ workItem.subtasks?.length ? `Subtasks: ${workItem.subtasks.map((subtask) => `${subtask.key} ${subtask.status}`).join(", ")}` : undefined,
3369
+ workItem.components?.length ? `Components: ${workItem.components.join(", ")}` : undefined,
3370
+ workItem.labels?.length ? `Labels: ${workItem.labels.join(", ")}` : undefined,
3371
+ workspace?.packageNames?.length ? `Workspace packages: ${workspace.packageNames.slice(0, 8).join(", ")}` : undefined,
3372
+ workspace?.codeownersKeywords?.length ? `Ownership signals: ${workspace.codeownersKeywords.slice(0, 8).join(", ")}` : undefined,
3373
+ workspace?.recentCommitRefs?.length ? `Recent related work refs: ${workspace.recentCommitRefs.slice(0, 8).join(", ")}` : undefined,
3374
+ repositoryKnowledge?.moduleBoundaries.length
3375
+ ? `Module boundaries: ${repositoryKnowledge.moduleBoundaries.slice(0, 8).map((boundary) => `${boundary.kind}:${boundary.path}`).join(", ")}`
3376
+ : undefined,
3377
+ repositoryKnowledge?.ownership.owners.length ? `Code owners: ${repositoryKnowledge.ownership.owners.slice(0, 8).join(", ")}` : undefined,
3378
+ repositoryKnowledge?.packageMap.validateCommands.length
3379
+ ? `Validation commands: ${repositoryKnowledge.packageMap.validateCommands.join(", ")}`
3380
+ : undefined
3381
+ ];
3382
+ return signals.filter((signal) => Boolean(signal)).slice(0, 10);
3383
+ }
3384
+ function inferWorkItemRisks(workItem, workspaceCandidate, acceptanceCriteria, likelyFiles) {
3385
+ const risks = [
3386
+ !workspaceCandidate ? "No codebase is selected yet; implementation cannot safely start." : undefined,
3387
+ workspaceCandidate && workspaceCandidate.confidence < 0.6 ? "Codebase match is weak; confirm workspace before editing files." : undefined,
3388
+ acceptanceCriteria.length === 0 ? "Acceptance criteria are not explicit; confirm completion rules before coding." : undefined,
3389
+ likelyFiles.length === 0 ? "No likely files were identified; repository knowledge should be built before a broad change." : undefined,
3390
+ /high|highest|blocker|critical/u.test((workItem.priority ?? "").toLowerCase()) ? `Priority is ${workItem.priority}; keep the change small and validation evidence strong.` : undefined,
3391
+ workItem.type === "bug" && detectMissingRequirementSignals(workItem).some((signal) => signal.includes("reproduction")) ? "Bug report lacks a clear reproduction path." : undefined,
3392
+ (workItem.subtasks?.length ?? 0) > 0 ? "Subtasks may affect delivery order and Jira completion update." : undefined
3393
+ ];
3394
+ return risks.filter((risk) => Boolean(risk)).slice(0, 8);
3395
+ }
3396
+ function inferTestStrategy(workItem, workspaceCandidate, likelyFiles, repositoryKnowledge) {
3397
+ const strategy = [
3398
+ workspaceCandidate?.workspace.path ? "Discover validation commands from the selected workspace." : "Resolve a workspace before selecting tests.",
3399
+ repositoryKnowledge?.packageMap.validateCommands.length
3400
+ ? `Prefer repository validation: ${repositoryKnowledge.packageMap.validateCommands.slice(0, 3).join(", ")}.`
3401
+ : undefined,
3402
+ repositoryKnowledge?.packageMap.testCommands.length
3403
+ ? `Run test command candidates: ${repositoryKnowledge.packageMap.testCommands.slice(0, 3).join(", ")}.`
3404
+ : undefined,
3405
+ repositoryKnowledge?.pathMap.tests.length
3406
+ ? "Repository knowledge found related test paths; run the narrowest affected test before broad validation."
3407
+ : undefined,
3408
+ likelyFiles.some((file) => /\b(test|spec)\b/u.test(file.path)) ? "Run the related test file candidate first." : undefined,
3409
+ workItem.type === "bug" ? "Add or run a regression test that reproduces the reported behavior." : undefined,
3410
+ workItem.type === "story" || workItem.type === "task" ? "Run the smallest relevant test plus the workspace validation command." : undefined,
3411
+ "Capture test evidence before PR creation.",
3412
+ "If validation fails, use the approved AI retry loop before broadening the patch."
3413
+ ];
3414
+ return strategy.filter((item) => Boolean(item));
3415
+ }
3416
+ function buildDeliveryChecklist(workItem) {
3417
+ return [
3418
+ `Keep ${workItem.key} in branch, commit, and PR title when possible.`,
3419
+ "Summarize implementation scope in the PR body.",
3420
+ "Include validation evidence from approved test runs.",
3421
+ "Call out risks, missing context, or manual QA needs.",
3422
+ "Post the Jira update only after reviewing the prepared message."
3423
+ ];
3424
+ }
3425
+ function inferCodePathFromUrl(value) {
3426
+ try {
3427
+ const url = new URL(value);
3428
+ const parts = url.pathname.split("/").filter(Boolean);
3429
+ const markerIndex = parts.findIndex((part) => part === "blob" || part === "tree");
3430
+ if (markerIndex >= 0 && parts.length > markerIndex + 2) {
3431
+ return parts.slice(markerIndex + 2).join("/");
3432
+ }
3433
+ }
3434
+ catch {
3435
+ // Non-URL code references are allowed below.
3436
+ }
3437
+ const trimmed = value.trim();
3438
+ if (/^[\w./-]+\.[a-z0-9]+(?::\d+)?$/iu.test(trimmed) && !trimmed.includes("://")) {
3439
+ return trimmed.replace(/:\d+$/u, "");
3440
+ }
3441
+ return undefined;
3442
+ }
3443
+ function slugifyPathSegment(value) {
3444
+ const slug = value.trim().toLowerCase().replace(/[^a-z0-9]+/gu, "-").replace(/^-|-$/gu, "");
3445
+ return slug.length >= 3 ? slug : undefined;
3446
+ }
3447
+ function uniqueFileHints(values) {
3448
+ const seen = new Set();
3449
+ const result = [];
3450
+ for (const value of values) {
3451
+ const key = value.path.toLowerCase();
3452
+ if (seen.has(key)) {
3453
+ continue;
3454
+ }
3455
+ seen.add(key);
3456
+ result.push(value);
3457
+ }
3458
+ return result;
3459
+ }
2816
3460
  function hasExplicitAcceptanceCriteria(workItem) {
2817
3461
  const text = [workItem.title, workItem.description].filter(Boolean).join("\n").toLowerCase();
2818
3462
  return /\b(acceptance criteria|acceptance|criteria|given\b.*\bwhen\b.*\bthen|expected result|definition of done|done when|should|expected behavior|success criteria|verify|validation)\b/su.test(text);
@@ -3097,6 +3741,19 @@ const sensitiveContentPatterns = [
3097
3741
  ];
3098
3742
  async function collectPatchContextFiles(workspacePath, session) {
3099
3743
  const candidates = [];
3744
+ const tokens = tokenizePatchSearchText([
3745
+ session.workItem.key,
3746
+ session.workItem.title,
3747
+ session.workItem.description,
3748
+ session.plan?.summary,
3749
+ ...(session.plan?.steps.map((step) => `${step.title} ${step.detail ?? ""}`) ?? []),
3750
+ ...(session.workItem.labels ?? []),
3751
+ ...(session.workItem.components ?? []),
3752
+ ...(session.workspaceCandidate?.workspace.packageNames ?? []),
3753
+ ...(session.workspaceCandidate?.workspace.readmeKeywords ?? []),
3754
+ ...(session.repositoryKnowledge?.packageMap.packageNames ?? []),
3755
+ ...(session.repositoryKnowledge?.moduleBoundaries.map((boundary) => `${boundary.name} ${boundary.path}`) ?? [])
3756
+ ].filter((value) => Boolean(value)).join(" "));
3100
3757
  for (const filePath of session.plan?.filesLikelyChanged ?? []) {
3101
3758
  const normalized = normalizeWorkspaceRelativePath(workspacePath, filePath, "skip");
3102
3759
  if (normalized && normalized !== ".") {
@@ -3108,18 +3765,33 @@ async function collectPatchContextFiles(workspacePath, session) {
3108
3765
  }
3109
3766
  }
3110
3767
  candidates.push({ filePath: "package.json", score: 24, reason: "Package metadata helps infer scripts, package boundaries, and runtime." }, { filePath: "README.md", score: 18, reason: "README gives repository purpose and local validation hints." }, { filePath: "AGENTS.md", score: 18, reason: "Agent instructions constrain safe implementation style." }, { filePath: "CODEOWNERS", score: 14, reason: "Ownership metadata helps identify relevant domains and review paths." });
3768
+ if (session.repositoryKnowledge) {
3769
+ for (const boundary of rankKnowledgeModuleBoundaries(session.repositoryKnowledge, tokens).slice(0, 8)) {
3770
+ candidates.push({
3771
+ filePath: boundary.path,
3772
+ score: 34,
3773
+ reason: `Repository knowledge selected ${boundary.kind} boundary "${boundary.name}".`
3774
+ });
3775
+ }
3776
+ for (const filePath of rankKnowledgePaths(session.repositoryKnowledge, tokens).slice(0, 30)) {
3777
+ candidates.push({
3778
+ filePath,
3779
+ score: 32 + scorePatchContextFile(filePath, tokens, new Set()),
3780
+ reason: "Repository knowledge ranked this tracked file for the current story."
3781
+ });
3782
+ }
3783
+ for (const filePath of [
3784
+ ...session.repositoryKnowledge.pathMap.config.slice(0, 12),
3785
+ ...session.repositoryKnowledge.pathMap.tests.slice(0, 12)
3786
+ ]) {
3787
+ candidates.push({
3788
+ filePath,
3789
+ score: 18,
3790
+ reason: "Repository knowledge includes this as relevant configuration or validation context."
3791
+ });
3792
+ }
3793
+ }
3111
3794
  const trackedFiles = await listTrackedWorkspaceFiles(workspacePath);
3112
- const tokens = tokenizePatchSearchText([
3113
- session.workItem.key,
3114
- session.workItem.title,
3115
- session.workItem.description,
3116
- session.plan?.summary,
3117
- ...(session.plan?.steps.map((step) => `${step.title} ${step.detail ?? ""}`) ?? []),
3118
- ...(session.workItem.labels ?? []),
3119
- ...(session.workItem.components ?? []),
3120
- ...(session.workspaceCandidate?.workspace.packageNames ?? []),
3121
- ...(session.workspaceCandidate?.workspace.readmeKeywords ?? [])
3122
- ].filter((value) => Boolean(value)).join(" "));
3123
3795
  const planHints = new Set((session.plan?.filesLikelyChanged ?? [])
3124
3796
  .map((filePath) => normalizeWorkspaceRelativePath(workspacePath, filePath, "skip"))
3125
3797
  .filter((filePath) => Boolean(filePath)));
@@ -3180,10 +3852,21 @@ async function listTrackedWorkspaceFiles(workspacePath) {
3180
3852
  .slice(0, 1000);
3181
3853
  return trackedFiles.length > 0 ? trackedFiles : listWorkspaceFilesFallback(workspacePath);
3182
3854
  }
3183
- async function listWorkspaceFilesFallback(workspacePath) {
3855
+ async function listWorkspaceFilesForKnowledge(workspacePath) {
3856
+ const output = await runGit(workspacePath, ["ls-files"]);
3857
+ const trackedFiles = output
3858
+ .split(/\r?\n/u)
3859
+ .map((line) => line.trim())
3860
+ .filter(Boolean)
3861
+ .filter((filePath) => !filePath.startsWith(".git/") && !filePath.startsWith("node_modules/") && !filePath.startsWith(".pome/"))
3862
+ .slice(0, 2000);
3863
+ return trackedFiles.length > 0 ? trackedFiles : listWorkspaceFilesFallback(workspacePath, { includeGenerated: true, includeSensitive: true });
3864
+ }
3865
+ async function listWorkspaceFilesFallback(workspacePath, options = {}) {
3184
3866
  const collected = [];
3185
3867
  const queue = ["."];
3186
- const ignoredDirectories = new Set([".git", "node_modules", "dist", "build", "coverage", ".next", ".turbo", ".cache", "vendor"]);
3868
+ const ignoredDirectories = new Set([".git", ".pome", "node_modules", "vendor"]);
3869
+ const generatedDirectories = new Set(["dist", "build", "coverage", ".next", ".turbo", ".cache", "out", "target", "generated", "__generated__"]);
3187
3870
  while (queue.length > 0 && collected.length < 1000) {
3188
3871
  const current = queue.shift() ?? ".";
3189
3872
  const absoluteCurrent = resolve(workspacePath, current);
@@ -3197,12 +3880,15 @@ async function listWorkspaceFilesFallback(workspacePath) {
3197
3880
  for await (const entry of directory) {
3198
3881
  const relativePath = current === "." ? entry.name : `${current}/${entry.name}`;
3199
3882
  if (entry.isDirectory()) {
3200
- if (!ignoredDirectories.has(entry.name) && !isSensitiveWorkspacePath(relativePath)) {
3883
+ const shouldSkipGenerated = !options.includeGenerated && generatedDirectories.has(entry.name);
3884
+ const shouldSkipSensitive = !options.includeSensitive && isSensitiveWorkspacePath(relativePath);
3885
+ if (!ignoredDirectories.has(entry.name) && !shouldSkipGenerated && !shouldSkipSensitive) {
3201
3886
  queue.push(relativePath);
3202
3887
  }
3203
3888
  continue;
3204
3889
  }
3205
- if (entry.isFile() && !isSensitiveWorkspacePath(relativePath)) {
3890
+ const shouldSkipSensitive = !options.includeSensitive && isSensitiveWorkspacePath(relativePath);
3891
+ if (entry.isFile() && !shouldSkipSensitive) {
3206
3892
  collected.push(relativePath);
3207
3893
  if (collected.length >= 1000) {
3208
3894
  break;
@@ -3276,6 +3962,7 @@ function buildStructuredPatchPrompt(session, workspacePath, contextFiles) {
3276
3962
  ...(plan?.missingInfo ?? [])
3277
3963
  ])).slice(0, 8);
3278
3964
  const workspace = session.workspaceCandidate?.workspace;
3965
+ const knowledge = session.repositoryKnowledge;
3279
3966
  return [
3280
3967
  "You are OpenPome's implementation engine.",
3281
3968
  failedTestContext.length
@@ -3322,6 +4009,18 @@ function buildStructuredPatchPrompt(session, workspacePath, contextFiles) {
3322
4009
  workspace?.recentBranches?.length ? `- Recent branches: ${workspace.recentBranches.slice(0, 8).join(", ")}` : undefined,
3323
4010
  workspace?.recentCommitRefs?.length ? `- Recent work refs: ${workspace.recentCommitRefs.slice(0, 12).join(", ")}` : undefined,
3324
4011
  "",
4012
+ knowledge ? "Repository knowledge:" : undefined,
4013
+ knowledge ? `- Knowledge file: ${knowledge.knowledgeFile}` : undefined,
4014
+ knowledge ? `- Package manager: ${knowledge.packageMap.packageManager}` : undefined,
4015
+ knowledge?.packageMap.packageNames.length ? `- Packages: ${knowledge.packageMap.packageNames.slice(0, 12).join(", ")}` : undefined,
4016
+ knowledge?.moduleBoundaries.length
4017
+ ? `- Module boundaries: ${knowledge.moduleBoundaries.slice(0, 12).map((boundary) => `${boundary.kind}:${boundary.path}`).join("; ")}`
4018
+ : undefined,
4019
+ knowledge ? `- Path map: ${knowledge.pathMap.source.length} source, ${knowledge.pathMap.tests.length} test, ${knowledge.pathMap.config.length} config, ${knowledge.pathMap.generated.length} generated, ${knowledge.pathMap.sensitive.length} sensitive` : undefined,
4020
+ knowledge?.ownership.owners.length ? `- Code owners: ${knowledge.ownership.owners.slice(0, 12).join(", ")}` : undefined,
4021
+ knowledge?.packageMap.validateCommands.length ? `- Validation commands: ${knowledge.packageMap.validateCommands.join(", ")}` : undefined,
4022
+ knowledge?.packageMap.testCommands.length ? `- Test commands: ${knowledge.packageMap.testCommands.join(", ")}` : undefined,
4023
+ knowledge ? "" : undefined,
3325
4024
  "Readable context files:",
3326
4025
  context || "No source files were safely included. You may propose small new files only if the task clearly asks for them."
3327
4026
  ].filter((line) => Boolean(line)).join("\n");
@@ -3502,7 +4201,9 @@ async function discoverTestCommandCandidates(workspacePath, session) {
3502
4201
  async function findRelatedTestFiles(workspacePath, session) {
3503
4202
  let trackedFiles = [];
3504
4203
  try {
3505
- trackedFiles = await listTrackedWorkspaceFiles(workspacePath);
4204
+ trackedFiles = session.repositoryKnowledge?.pathMap.tests.length
4205
+ ? session.repositoryKnowledge.pathMap.tests
4206
+ : await listTrackedWorkspaceFiles(workspacePath);
3506
4207
  }
3507
4208
  catch {
3508
4209
  return [];
@@ -3518,6 +4219,8 @@ async function findRelatedTestFiles(workspacePath, session) {
3518
4219
  session.workItem.description,
3519
4220
  ...(session.workItem.labels ?? []),
3520
4221
  ...(session.workItem.components ?? []),
4222
+ ...(session.repositoryKnowledge?.packageMap.packageNames ?? []),
4223
+ ...(session.repositoryKnowledge?.moduleBoundaries.map((boundary) => `${boundary.name} ${boundary.path}`) ?? []),
3521
4224
  session.plan?.summary
3522
4225
  ].filter((value) => Boolean(value)).join(" "))
3523
4226
  ]);