@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.
- package/CHANGELOG.md +43 -1
- package/README.md +19 -14
- package/dist/behavior-intent.d.ts +6 -0
- package/dist/behavior-intent.js +172 -0
- package/dist/behavior-intent.js.map +1 -0
- package/dist/behavior-manifest.d.ts +6 -0
- package/dist/behavior-manifest.js +221 -0
- package/dist/behavior-manifest.js.map +1 -0
- package/dist/behavior.d.ts +143 -0
- package/dist/behavior.js +458 -0
- package/dist/behavior.js.map +1 -0
- package/dist/change-intent.d.ts +71 -0
- package/dist/change-intent.js +744 -0
- package/dist/change-intent.js.map +1 -0
- package/dist/cli.js +6 -5
- package/dist/cli.js.map +1 -1
- package/dist/e2e.d.ts +16 -1
- package/dist/e2e.js +329 -24
- package/dist/e2e.js.map +1 -1
- package/dist/index.d.ts +9 -1
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/manifest.js +138 -4
- package/dist/manifest.js.map +1 -1
- package/dist/qa.d.ts +1 -0
- package/dist/qa.js +73 -1
- package/dist/qa.js.map +1 -1
- package/dist/terminal.js +1 -1
- package/dist/terminal.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/docs/adoption.md +11 -10
- package/docs/agent-format.md +9 -7
- package/docs/agent-skill.md +5 -2
- package/docs/architecture.md +131 -0
- package/docs/benchmarking.md +26 -4
- package/docs/commands.md +13 -7
- package/docs/e2e-output-examples.md +23 -9
- package/docs/manifest.md +4 -0
- package/docs/quickstart-demo.md +9 -2
- package/docs/release-validation.md +50 -37
- package/docs/releasing.md +13 -0
- package/docs/roadmap.md +20 -10
- package/package.json +4 -2
- package/schema/qamap-agent.schema.json +42 -1
- package/schema/qamap-behavior.schema.json +307 -0
- 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
|
|
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
|
|
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/
|
|
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) {
|