@ivorycanvas/qamap 0.3.4 → 0.4.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 (47) hide show
  1. package/CHANGELOG.md +43 -1
  2. package/README.md +19 -14
  3. package/dist/behavior-intent.d.ts +6 -0
  4. package/dist/behavior-intent.js +172 -0
  5. package/dist/behavior-intent.js.map +1 -0
  6. package/dist/behavior-manifest.d.ts +6 -0
  7. package/dist/behavior-manifest.js +221 -0
  8. package/dist/behavior-manifest.js.map +1 -0
  9. package/dist/behavior.d.ts +143 -0
  10. package/dist/behavior.js +458 -0
  11. package/dist/behavior.js.map +1 -0
  12. package/dist/change-intent.d.ts +71 -0
  13. package/dist/change-intent.js +744 -0
  14. package/dist/change-intent.js.map +1 -0
  15. package/dist/cli.js +6 -5
  16. package/dist/cli.js.map +1 -1
  17. package/dist/e2e.d.ts +16 -1
  18. package/dist/e2e.js +329 -24
  19. package/dist/e2e.js.map +1 -1
  20. package/dist/index.d.ts +9 -1
  21. package/dist/index.js +4 -0
  22. package/dist/index.js.map +1 -1
  23. package/dist/manifest.js +138 -4
  24. package/dist/manifest.js.map +1 -1
  25. package/dist/qa.d.ts +1 -0
  26. package/dist/qa.js +73 -1
  27. package/dist/qa.js.map +1 -1
  28. package/dist/terminal.js +1 -1
  29. package/dist/terminal.js.map +1 -1
  30. package/dist/version.d.ts +1 -1
  31. package/dist/version.js +1 -1
  32. package/docs/adoption.md +11 -10
  33. package/docs/agent-format.md +9 -7
  34. package/docs/agent-skill.md +5 -2
  35. package/docs/architecture.md +131 -0
  36. package/docs/benchmarking.md +26 -4
  37. package/docs/commands.md +13 -7
  38. package/docs/e2e-output-examples.md +23 -9
  39. package/docs/manifest.md +4 -0
  40. package/docs/quickstart-demo.md +9 -2
  41. package/docs/release-validation.md +50 -37
  42. package/docs/releasing.md +13 -0
  43. package/docs/roadmap.md +20 -10
  44. package/package.json +4 -2
  45. package/schema/qamap-agent.schema.json +42 -1
  46. package/schema/qamap-behavior.schema.json +307 -0
  47. package/skills/qamap-pr-qa/SKILL.md +16 -11
package/dist/index.d.ts CHANGED
@@ -1,4 +1,8 @@
1
1
  export { buildAgentQaSection, formatAgentInitReport, initAgentSetup } from "./agent-init.js";
2
+ export { analyzeBehaviorGraph, behaviorEdgeKinds, behaviorEvidenceKinds, behaviorGraphSchemaVersion, behaviorGraphSchemaUrl, behaviorNodeKinds, behaviorSurfaceKinds, createBehaviorEdge, createBehaviorNodeId, createInferredFlowBehaviorAdapter, mergeBehaviorGraphFragments, } from "./behavior.js";
3
+ export { createManifestBehaviorAdapter } from "./behavior-manifest.js";
4
+ export { createChangeIntentBehaviorAdapter } from "./behavior-intent.js";
5
+ export { analyzeChangeIntents } from "./change-intent.js";
2
6
  export { defaultConfig, loadConfig, writeDefaultConfig } from "./config.js";
3
7
  export { detectDlxCommand, generateAgentContext } from "./context.js";
4
8
  export { buildDomainLanguageSummary } from "./domain-language.js";
@@ -18,11 +22,15 @@ export { scanProject } from "./scanner.js";
18
22
  export { collectTestSuiteInventory, evaluateFlowCoverageEvidence, summarizeTestSuiteInventory } from "./test-evidence.js";
19
23
  export { formatMarkdownTestPlan, generateTestPlan } from "./test-plan.js";
20
24
  export { formatMarkdownVerifyReport, formatVerifyReport, verifyChange } from "./verify.js";
25
+ export type { BehaviorAdapterConfidence, BehaviorAdapterDetection, BehaviorAdapterRun, BehaviorAnalysisContext, BehaviorAnalyzerAdapter, BehaviorAttributeValue, BehaviorChangedFile, BehaviorConfidence, BehaviorDiagnostic, BehaviorDiagnosticSeverity, BehaviorEdge, BehaviorEdgeKind, BehaviorEvidence, BehaviorEvidenceKind, BehaviorGraph, BehaviorGraphFragment, BehaviorGraphSummary, BehaviorImpact, BehaviorImpactKind, BehaviorNode, BehaviorNodeKind, BehaviorSurfaceKind, InferredBehaviorCoverage, InferredBehaviorEntrypoint, InferredBehaviorFlow, InferredBehaviorSelector, InferredFlowAdapterOptions, } from "./behavior.js";
26
+ export type { ManifestBehaviorAdapterOptions } from "./behavior-manifest.js";
27
+ export type { ChangeIntentBehaviorAdapterOptions } from "./behavior-intent.js";
28
+ export type { BehaviorLifecycleStage, BehaviorLifecycleStageKind, ChangeIntent, ChangeIntentAnalysis, ChangeIntentAnalysisOptions, ChangeIntentCommit, ChangeIntentConfidence, ChangeIntentEvidence, ChangeIntentEvidenceKind, IntentQaScenario, IntentQaScenarioKind, IntentQaScenarioPriority, } from "./change-intent.js";
21
29
  export type { CoverageEvidence, CoverageEvidenceConfidence, CoverageEvidenceStatus, FlowCoverageInput, TestSuiteEvidenceFile, TestSuiteInventory, TestSuiteSummary, } from "./test-evidence.js";
22
30
  export type { DoctorArea, DoctorPriority, DoctorResult } from "./doctor.js";
23
31
  export type { DomainLanguageConfidence, DomainLanguageSource, DomainLanguageSummary, DomainLanguageTerm, DomainScenarioSuggestion, } from "./domain-language.js";
24
32
  export type { DomainDefinition, DomainManifest, DomainScenarioDefinition, MatchedDomain, } from "./domains.js";
25
- export type { E2eCoveragePriority, E2eCoverageTarget, E2eBootstrapPlan, E2eBootstrapStep, E2eBootstrapStepCategory, E2eBootstrapStepStatus, E2eEntrypoint, E2eEntrypointConfidence, E2eEntrypointKind, E2eFixtureReadiness, E2eFixtureReadinessStatus, E2eFlow, E2eSelector, E2eSelectorKind, E2eSetupHint, E2eSetupHintConfidence, E2eSetupHintKind, E2eValidationMatrix, E2eValidationMatrixCategory, E2eValidationMatrixRow, E2eValidationMatrixStatus, E2eDraftFile, E2eDraftOptions, E2eDraftResult, E2ePlanOptions, E2ePlanResult, E2eProjectProfile, E2eProjectType, E2eRunnerName, E2eRunnerRecommendation, E2eRunnerSetupProposal, E2eRunnerSetupStatus, E2eSetupOptions, E2eSetupResult, } from "./e2e.js";
33
+ export type { E2eCoveragePriority, E2eCoverageTarget, E2eBootstrapPlan, E2eBootstrapStep, E2eBootstrapStepCategory, E2eBootstrapStepStatus, E2eEntrypoint, E2eEntrypointConfidence, E2eEntrypointKind, E2eFixtureReadiness, E2eFixtureReadinessStatus, E2eFlow, E2eFlowKind, E2eSelector, E2eSelectorKind, E2eSetupHint, E2eSetupHintConfidence, E2eSetupHintKind, E2eValidationMatrix, E2eValidationMatrixCategory, E2eValidationMatrixRow, E2eValidationMatrixStatus, E2eDraftFile, E2eDraftOptions, E2eDraftResult, E2ePlanOptions, E2ePlanResult, E2eProjectProfile, E2eProjectType, E2eRunnerName, E2eRunnerRecommendation, E2eRunnerSetupProposal, E2eRunnerSetupStatus, E2eSetupOptions, E2eSetupResult, } from "./e2e.js";
26
34
  export type { EvalCheck, EvalCheckStatus, EvalOptions, EvalRating, EvalResult } from "./eval.js";
27
35
  export type { CoreFlowDefinition, CoreFlowManifest, CoreFlowPriority, MatchedCoreFlow, } from "./flows.js";
28
36
  export type { GitHubActionMode, GitHubActionOptions, GitHubActionResult } from "./github.js";
package/dist/index.js CHANGED
@@ -1,4 +1,8 @@
1
1
  export { buildAgentQaSection, formatAgentInitReport, initAgentSetup } from "./agent-init.js";
2
+ export { analyzeBehaviorGraph, behaviorEdgeKinds, behaviorEvidenceKinds, behaviorGraphSchemaVersion, behaviorGraphSchemaUrl, behaviorNodeKinds, behaviorSurfaceKinds, createBehaviorEdge, createBehaviorNodeId, createInferredFlowBehaviorAdapter, mergeBehaviorGraphFragments, } from "./behavior.js";
3
+ export { createManifestBehaviorAdapter } from "./behavior-manifest.js";
4
+ export { createChangeIntentBehaviorAdapter } from "./behavior-intent.js";
5
+ export { analyzeChangeIntents } from "./change-intent.js";
2
6
  export { defaultConfig, loadConfig, writeDefaultConfig } from "./config.js";
3
7
  export { detectDlxCommand, generateAgentContext } from "./context.js";
4
8
  export { buildDomainLanguageSummary } from "./domain-language.js";
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,qBAAqB,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAC7F,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAC5E,OAAO,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAC;AACtE,OAAO,EAAE,0BAA0B,EAAE,MAAM,sBAAsB,CAAC;AAClE,OAAO,EACL,yBAAyB,EACzB,kBAAkB,EAClB,YAAY,EACZ,0BAA0B,GAC3B,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,0BAA0B,EAAE,MAAM,aAAa,CAAC;AAChG,OAAO,EACL,sBAAsB,EACtB,qBAAqB,EACrB,sBAAsB,EACtB,gBAAgB,EAChB,eAAe,EACf,cAAc,GACf,MAAM,UAAU,CAAC;AAClB,OAAO,EAAE,uBAAuB,EAAE,gBAAgB,EAAE,wBAAwB,EAAE,MAAM,WAAW,CAAC;AAChG,OAAO,EACL,uBAAuB,EACvB,oBAAoB,EACpB,cAAc,EACd,4BAA4B,GAC7B,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,mBAAmB,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AACnE,OAAO,EACL,kBAAkB,EAClB,4BAA4B,EAC5B,sBAAsB,EACtB,mBAAmB,EACnB,qBAAqB,EACrB,6BAA6B,EAC7B,iBAAiB,EACjB,oBAAoB,GACrB,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,kCAAkC,EAClC,gCAAgC,EAChC,8BAA8B,EAC9B,4BAA4B,EAC5B,gCAAgC,EAChC,8BAA8B,EAC9B,sBAAsB,GACvB,MAAM,2BAA2B,CAAC;AACnC,OAAO,EACL,kCAAkC,EAClC,kCAAkC,EAClC,+BAA+B,EAC/B,2BAA2B,EAC3B,uCAAuC,EACvC,uCAAuC,EACvC,oCAAoC,EACpC,0CAA0C,EAC1C,8BAA8B,EAC9B,wBAAwB,EACxB,yBAAyB,EACzB,4BAA4B,EAC5B,6BAA6B,EAC7B,iCAAiC,GAClC,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,oBAAoB,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAC9G,OAAO,EAAE,kBAAkB,EAAE,qBAAqB,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AACrF,OAAO,EAAE,0BAA0B,EAAE,kBAAkB,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5F,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAC3C,OAAO,EAAE,yBAAyB,EAAE,4BAA4B,EAAE,2BAA2B,EAAE,MAAM,oBAAoB,CAAC;AAC1H,OAAO,EAAE,sBAAsB,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAC1E,OAAO,EAAE,0BAA0B,EAAE,kBAAkB,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,qBAAqB,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAC7F,OAAO,EACL,oBAAoB,EACpB,iBAAiB,EACjB,qBAAqB,EACrB,0BAA0B,EAC1B,sBAAsB,EACtB,iBAAiB,EACjB,oBAAoB,EACpB,kBAAkB,EAClB,oBAAoB,EACpB,iCAAiC,EACjC,2BAA2B,GAC5B,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,6BAA6B,EAAE,MAAM,wBAAwB,CAAC;AACvE,OAAO,EAAE,iCAAiC,EAAE,MAAM,sBAAsB,CAAC;AACzE,OAAO,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAC;AAC1D,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAC5E,OAAO,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAC;AACtE,OAAO,EAAE,0BAA0B,EAAE,MAAM,sBAAsB,CAAC;AAClE,OAAO,EACL,yBAAyB,EACzB,kBAAkB,EAClB,YAAY,EACZ,0BAA0B,GAC3B,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,0BAA0B,EAAE,MAAM,aAAa,CAAC;AAChG,OAAO,EACL,sBAAsB,EACtB,qBAAqB,EACrB,sBAAsB,EACtB,gBAAgB,EAChB,eAAe,EACf,cAAc,GACf,MAAM,UAAU,CAAC;AAClB,OAAO,EAAE,uBAAuB,EAAE,gBAAgB,EAAE,wBAAwB,EAAE,MAAM,WAAW,CAAC;AAChG,OAAO,EACL,uBAAuB,EACvB,oBAAoB,EACpB,cAAc,EACd,4BAA4B,GAC7B,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,mBAAmB,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AACnE,OAAO,EACL,kBAAkB,EAClB,4BAA4B,EAC5B,sBAAsB,EACtB,mBAAmB,EACnB,qBAAqB,EACrB,6BAA6B,EAC7B,iBAAiB,EACjB,oBAAoB,GACrB,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,kCAAkC,EAClC,gCAAgC,EAChC,8BAA8B,EAC9B,4BAA4B,EAC5B,gCAAgC,EAChC,8BAA8B,EAC9B,sBAAsB,GACvB,MAAM,2BAA2B,CAAC;AACnC,OAAO,EACL,kCAAkC,EAClC,kCAAkC,EAClC,+BAA+B,EAC/B,2BAA2B,EAC3B,uCAAuC,EACvC,uCAAuC,EACvC,oCAAoC,EACpC,0CAA0C,EAC1C,8BAA8B,EAC9B,wBAAwB,EACxB,yBAAyB,EACzB,4BAA4B,EAC5B,6BAA6B,EAC7B,iCAAiC,GAClC,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,oBAAoB,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAC9G,OAAO,EAAE,kBAAkB,EAAE,qBAAqB,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AACrF,OAAO,EAAE,0BAA0B,EAAE,kBAAkB,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5F,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAC3C,OAAO,EAAE,yBAAyB,EAAE,4BAA4B,EAAE,2BAA2B,EAAE,MAAM,oBAAoB,CAAC;AAC1H,OAAO,EAAE,sBAAsB,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAC1E,OAAO,EAAE,0BAA0B,EAAE,kBAAkB,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC"}
package/dist/manifest.js CHANGED
@@ -384,15 +384,22 @@ export function formatVerificationManifestYaml(manifest) {
384
384
  }
385
385
  function buildVerificationManifestBaseline(root, manifestRoot, files) {
386
386
  const context = buildManifestContext(root, manifestRoot, files);
387
- const behaviorFiles = files
388
- .filter((file) => isBehaviorFile(file.path))
387
+ const manifestFiles = files
389
388
  .map((file) => ({
390
389
  ...file,
391
390
  path: toPosixPath(path.relative(manifestRoot, path.join(root, file.path))),
392
391
  }))
393
392
  .filter((file) => !file.path.startsWith("../"));
393
+ const behaviorFiles = manifestFiles.filter((file) => isBehaviorFile(file.path));
394
394
  const domains = mergeDomainsById(buildBaselineDomains(behaviorFiles, context), buildPythonAppDomains(files, manifestRoot, root)).slice(0, 12);
395
- const flows = buildBaselineFlows(behaviorFiles, domains, inferRunner(files), context).slice(0, 16);
395
+ const runner = inferRunner(files);
396
+ const uiFlows = buildBaselineFlows(behaviorFiles, domains, runner, context);
397
+ const apiFlows = buildBaselineApiFlows(manifestFiles, domains, runner);
398
+ const flows = apiFlows.length === 0
399
+ ? uiFlows.slice(0, 16)
400
+ : uiFlows.length === 0
401
+ ? apiFlows.slice(0, 16)
402
+ : dedupeFlows([...uiFlows.slice(0, 12), ...apiFlows.slice(0, 4)]).slice(0, 16);
396
403
  return {
397
404
  $schema: verificationManifestSchemaUrl,
398
405
  version: 1,
@@ -1063,6 +1070,133 @@ function buildBaselineFlows(files, domains, runner, context) {
1063
1070
  scored.sort((left, right) => right.score - left.score);
1064
1071
  return interleaveFlowsByDomain(dedupeFlows(scored.map((entry) => entry.flow)));
1065
1072
  }
1073
+ function buildBaselineApiFlows(files, domains, runner) {
1074
+ const serviceProject = runner === "manual" && hasApiServiceSignal(files);
1075
+ const grouped = new Map();
1076
+ for (const file of files) {
1077
+ if (!isApiBehaviorFile(file.path, runner, serviceProject)) {
1078
+ continue;
1079
+ }
1080
+ const domain = bestDomainForFile(domains, file.path);
1081
+ const subject = domain?.name ?? apiSubjectFromFile(file.path);
1082
+ const id = slugify(`${domain?.id ?? subject} api contract`);
1083
+ if (!id) {
1084
+ continue;
1085
+ }
1086
+ const symbol = /\.[cm]?[jt]sx?$/.test(file.path) && file.text
1087
+ ? (exportedSymbolFromText(file.text, path.basename(file.path).replace(/\.[^.]+$/, "")) ?? undefined)
1088
+ : undefined;
1089
+ const anchor = {
1090
+ kind: "api",
1091
+ path: file.path,
1092
+ symbol,
1093
+ source: "inferred",
1094
+ confidence: symbol ? "high" : "medium",
1095
+ };
1096
+ const existing = grouped.get(id);
1097
+ if (existing) {
1098
+ if (!existing.anchors.some((candidate) => candidate.path === anchor.path)) {
1099
+ existing.anchors.push(anchor);
1100
+ }
1101
+ continue;
1102
+ }
1103
+ grouped.set(id, {
1104
+ domain,
1105
+ name: `${subject} API contract`,
1106
+ anchors: [anchor],
1107
+ });
1108
+ }
1109
+ return [...grouped.entries()]
1110
+ .map(([id, value]) => ({
1111
+ id,
1112
+ domain: value.domain?.id,
1113
+ name: value.name,
1114
+ runner: "manual",
1115
+ anchors: value.anchors.slice(0, 6),
1116
+ checks: [
1117
+ {
1118
+ id: "response-contract",
1119
+ title: `Return the expected ${value.name.replace(/ API contract$/, "")} response contract`,
1120
+ type: "contract",
1121
+ },
1122
+ {
1123
+ id: "invalid-request",
1124
+ title: "Reject invalid requests without partial side effects",
1125
+ type: "failure",
1126
+ },
1127
+ ],
1128
+ source: {
1129
+ kind: "inferred",
1130
+ confidence: "medium",
1131
+ from: ["api-route-file"],
1132
+ },
1133
+ }))
1134
+ .sort((left, right) => right.anchors.length - left.anchors.length || left.id.localeCompare(right.id));
1135
+ }
1136
+ function isApiBehaviorFile(file, runner, serviceProject) {
1137
+ const normalized = toPosixPath(file);
1138
+ if (/(?:^|\/)(?:tests?|__tests__|e2e|dist|build|out|\.output|__generated__)(?:\/|$)/i.test(normalized)) {
1139
+ return false;
1140
+ }
1141
+ if (runner !== "manual") {
1142
+ return false;
1143
+ }
1144
+ return (/(?:^|\/)(?:api|routes?|controllers?|handlers?)\/.+\.(?:[cm]?[jt]s|py|go|rs|java|kt|cs|rb)$/i.test(normalized) ||
1145
+ /(?:^|\/)(?:api|routes?|controllers?|handlers?|views)(?:\/[^/]+|[_-][^/]+)?\.py$/i.test(normalized) ||
1146
+ /(?:^|\/)(?:urls|views|api|controllers?|handlers?)\.py$/i.test(normalized) ||
1147
+ (serviceProject && isNamedServiceModule(normalized)));
1148
+ }
1149
+ function isNamedServiceModule(file) {
1150
+ return (/(?:^|\/)src\/(?:.*\/)?(?:domains?|features?|modules?|services?)\/.+\.(?:[cm]?[jt]s|py|go|rs|java|kt|cs|rb)$/i.test(file) ||
1151
+ /(?:^|\/)[^/]*(?:controller|endpoint|gateway|handler|repository|resolver|router|service|use-?case)[^/]*\.(?:[cm]?[jt]s|py|go|rs|java|kt|cs|rb)$/i.test(file));
1152
+ }
1153
+ function hasApiServiceSignal(files) {
1154
+ const serverDependencies = new Set([
1155
+ "@hapi/hapi",
1156
+ "@nestjs/core",
1157
+ "express",
1158
+ "fastify",
1159
+ "hono",
1160
+ "koa",
1161
+ "restify",
1162
+ ]);
1163
+ for (const file of files) {
1164
+ if (!file.text) {
1165
+ continue;
1166
+ }
1167
+ const basename = path.basename(file.path).toLowerCase();
1168
+ if (basename === "package.json") {
1169
+ try {
1170
+ const parsed = JSON.parse(file.text);
1171
+ const dependencies = { ...parsed.dependencies, ...parsed.devDependencies };
1172
+ if ([...serverDependencies].some((dependency) => dependencies[dependency] !== undefined)) {
1173
+ return true;
1174
+ }
1175
+ }
1176
+ catch {
1177
+ continue;
1178
+ }
1179
+ }
1180
+ if (["requirements.txt", "pyproject.toml", "pipfile"].includes(basename) &&
1181
+ /\b(?:django|fastapi|flask|litestar|sanic|starlette)\b/i.test(file.text)) {
1182
+ return true;
1183
+ }
1184
+ if (basename === "go.mod" && /\b(?:gin-gonic|gofiber|labstack\/echo)\b/i.test(file.text)) {
1185
+ return true;
1186
+ }
1187
+ if (basename === "cargo.toml" && /\b(?:actix-web|axum|rocket)\b/i.test(file.text)) {
1188
+ return true;
1189
+ }
1190
+ }
1191
+ return false;
1192
+ }
1193
+ function apiSubjectFromFile(file) {
1194
+ const segments = toPosixPath(file).split("/").filter(Boolean);
1195
+ const basename = (segments.at(-1) ?? "api").replace(/\.[^.]+$/, "");
1196
+ const generic = /^(?:api|controller|controllers|handler|handlers|index|route|routes|urls|view|views)$/i;
1197
+ const candidate = generic.test(basename) ? (segments.at(-2) ?? "api") : basename;
1198
+ return titleCase(candidate) || "API";
1199
+ }
1066
1200
  const productSignalMatcher = /login|signin|sign-in|signup|sign-up|register|auth|password|payment|pay\b|checkout|billing|subscription|purchase|order|cart|onboarding|settings|account|profile|search|upload|home/i;
1067
1201
  // Rank flow candidates by product signal so the flow cap keeps core journeys
1068
1202
  // instead of whichever files sort first alphabetically.
@@ -1632,7 +1766,7 @@ function domainCandidateFromPath(file) {
1632
1766
  }
1633
1767
  }
1634
1768
  // The basename may only speak for itself when the file sits directly
1635
- // under the route dir (app/SentimentChatPage.tsx), not when a
1769
+ // under the route dir (app/SampleChatPage.tsx), not when a
1636
1770
  // structural directory owns the subtree. Router-convention names
1637
1771
  // (_layout, +not-found, (group)) never qualify.
1638
1772
  if (segments.length === routeIndex + 2) {