@topogram/cli 0.3.77 → 0.3.79
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 +20 -0
- package/package.json +2 -2
- package/src/agent-brief.js +29 -23
- package/src/agent-ops/query-builders/change-risk/{import-plan.js → extract-plan.js} +1 -1
- package/src/agent-ops/query-builders/change-risk/review-packets.js +5 -5
- package/src/agent-ops/query-builders/change-risk.js +1 -1
- package/src/agent-ops/query-builders/common.js +2 -2
- package/src/agent-ops/query-builders/multi-agent.js +1 -1
- package/src/agent-ops/query-builders/workflow-context-shared.js +4 -4
- package/src/catalog/provenance.js +1 -1
- package/src/cli/catalog-alias.d.ts +2 -0
- package/src/cli/catalog-alias.js +2 -2
- package/src/cli/command-parsers/core.js +9 -5
- package/src/cli/command-parsers/import.js +11 -17
- package/src/cli/command-parsers/project.js +0 -3
- package/src/cli/commands/catalog/copy.js +3 -3
- package/src/cli/commands/catalog/help.js +1 -2
- package/src/cli/commands/catalog/list.js +7 -4
- package/src/cli/commands/catalog/show.js +4 -4
- package/src/cli/commands/copy.js +356 -0
- package/src/cli/commands/doctor.js +1 -1
- package/src/cli/commands/import/adopt.js +9 -9
- package/src/cli/commands/import/check.js +15 -15
- package/src/cli/commands/import/diff.js +6 -6
- package/src/cli/commands/import/help.js +43 -34
- package/src/cli/commands/import/paths.js +3 -3
- package/src/cli/commands/import/plan.js +8 -8
- package/src/cli/commands/import/refresh.js +25 -24
- package/src/cli/commands/import/status-history.js +4 -4
- package/src/cli/commands/import/workspace.js +16 -16
- package/src/cli/commands/import-runner.js +6 -5
- package/src/cli/commands/import.js +4 -1
- package/src/cli/commands/init.js +67 -0
- package/src/cli/commands/query/{import-adopt.js → extract-adopt.js} +2 -2
- package/src/cli/commands/query/runner/change.js +2 -2
- package/src/cli/commands/query/runner/{import-adopt.js → extract-adopt.js} +9 -9
- package/src/cli/commands/query/runner/index.js +1 -1
- package/src/cli/commands/query/runner/workflow.js +7 -7
- package/src/cli/commands/query/workspace.js +4 -4
- package/src/cli/commands/release-status.js +2 -2
- package/src/cli/commands/source.js +2 -2
- package/src/cli/commands/template/check.js +2 -2
- package/src/cli/commands/template/list-show.js +4 -4
- package/src/cli/dispatcher.js +18 -3
- package/src/cli/help-dispatch.js +22 -8
- package/src/cli/help.js +68 -52
- package/src/cli/migration-guidance.js +9 -0
- package/src/generator/context/bundle.js +14 -7
- package/src/generator/context/diff.js +8 -1
- package/src/generator/context/digest.js +10 -1
- package/src/generator/context/shared/domain-sdlc.js +5 -1
- package/src/generator/context/shared/relationships.js +20 -5
- package/src/generator/context/shared/summaries.js +26 -0
- package/src/generator/context/shared.d.ts +1 -0
- package/src/generator/context/shared.js +1 -0
- package/src/generator/context/slice/core.js +9 -5
- package/src/generator/context/slice/sdlc.js +31 -2
- package/src/generator/context/task-mode.js +3 -3
- package/src/import/core/runner/reports.js +4 -4
- package/src/import/core/shared/files.js +21 -2
- package/src/import/core/shared.js +2 -1
- package/src/import/enrichers/django-rest.js +4 -4
- package/src/import/enrichers/rails-controllers.js +3 -3
- package/src/import/enrichers/rails-models.js +3 -3
- package/src/import/extractors/api/aspnet-core.js +5 -5
- package/src/import/extractors/api/django-routes.js +5 -5
- package/src/import/extractors/api/express.js +4 -4
- package/src/import/extractors/api/fastify.js +7 -7
- package/src/import/extractors/api/flutter-dio.js +4 -4
- package/src/import/extractors/api/generic-route-fallback.js +2 -2
- package/src/import/extractors/api/graphql-code-first.js +3 -3
- package/src/import/extractors/api/graphql-sdl.js +5 -5
- package/src/import/extractors/api/jaxrs.js +3 -3
- package/src/import/extractors/api/micronaut.js +3 -3
- package/src/import/extractors/api/openapi-code.js +4 -4
- package/src/import/extractors/api/openapi.js +3 -3
- package/src/import/extractors/api/rails-routes.js +3 -3
- package/src/import/extractors/api/react-native-repository.js +3 -3
- package/src/import/extractors/api/retrofit.js +3 -3
- package/src/import/extractors/api/spring-web.js +3 -3
- package/src/import/extractors/api/swift-webapi.js +3 -3
- package/src/import/extractors/api/trpc.js +4 -4
- package/src/import/extractors/cli/generic.js +3 -3
- package/src/import/extractors/db/django-models.js +4 -4
- package/src/import/extractors/db/dotnet-models.js +4 -4
- package/src/import/extractors/db/drizzle.js +9 -7
- package/src/import/extractors/db/ef-core.js +5 -5
- package/src/import/extractors/db/flutter-entities.js +3 -3
- package/src/import/extractors/db/jpa.js +3 -3
- package/src/import/extractors/db/liquibase.js +3 -3
- package/src/import/extractors/db/maintained-seams.js +4 -4
- package/src/import/extractors/db/mybatis-xml.js +4 -4
- package/src/import/extractors/db/prisma.js +3 -3
- package/src/import/extractors/db/rails-schema.js +3 -3
- package/src/import/extractors/db/react-native-entities.js +3 -3
- package/src/import/extractors/db/room.js +5 -5
- package/src/import/extractors/db/snapshot.js +3 -3
- package/src/import/extractors/db/sql.js +3 -3
- package/src/import/extractors/db/swiftdata.js +3 -3
- package/src/import/extractors/ui/android-compose.js +4 -4
- package/src/import/extractors/ui/backend-only.js +3 -3
- package/src/import/extractors/ui/blazor.js +3 -3
- package/src/import/extractors/ui/flutter-screens.js +3 -3
- package/src/import/extractors/ui/maui-xaml.js +4 -4
- package/src/import/extractors/ui/next-pages-router.js +3 -3
- package/src/import/extractors/ui/razor-pages.js +3 -3
- package/src/import/extractors/ui/react-native-screens.js +4 -4
- package/src/import/extractors/ui/swiftui.js +3 -3
- package/src/import/extractors/ui/uikit.js +3 -3
- package/src/import/provenance.js +16 -16
- package/src/init-project.js +215 -0
- package/src/new-project/constants.js +1 -1
- package/src/new-project/create.js +2 -2
- package/src/new-project/project-files.js +7 -7
- package/src/reconcile/journeys.js +8 -3
- package/src/record-blocks.js +125 -0
- package/src/resolver/index.js +3 -0
- package/src/resolver/journeys.js +74 -0
- package/src/resolver/normalize.js +25 -0
- package/src/sdlc/adopt.js +1 -1
- package/src/validator/common.js +34 -1
- package/src/validator/index.js +4 -0
- package/src/validator/kinds.d.ts +2 -0
- package/src/validator/kinds.js +34 -1
- package/src/validator/per-kind/journey.js +233 -0
- package/src/workflows/docs-generate.js +4 -1
- package/src/workflows/reconcile/bundle-core/index.js +4 -2
- package/src/workflows/reconcile/canonical-surface.js +4 -1
- package/src/cli/commands/new.js +0 -94
|
@@ -97,6 +97,30 @@ export function summarizeJourneyDoc(doc) {
|
|
|
97
97
|
};
|
|
98
98
|
}
|
|
99
99
|
|
|
100
|
+
/**
|
|
101
|
+
* @param {import("./types.d.ts").ContextStatement} journey
|
|
102
|
+
* @returns {any}
|
|
103
|
+
*/
|
|
104
|
+
export function summarizeJourney(journey) {
|
|
105
|
+
return {
|
|
106
|
+
id: journey.id,
|
|
107
|
+
kind: journey.kind,
|
|
108
|
+
name: journey.name || journey.id,
|
|
109
|
+
description: journey.description || null,
|
|
110
|
+
status: journey.status || null,
|
|
111
|
+
goal: journey.goal || null,
|
|
112
|
+
actors: stableSortedStrings(journey.actors || []),
|
|
113
|
+
relatedCapabilities: stableSortedStrings(journey.relatedCapabilities || []),
|
|
114
|
+
relatedWorkflows: stableSortedStrings(journey.relatedWorkflows || []),
|
|
115
|
+
relatedProjections: stableSortedStrings(journey.relatedProjections || []),
|
|
116
|
+
relatedWidgets: stableSortedStrings(journey.relatedWidgets || []),
|
|
117
|
+
stepCount: (journey.steps || []).length,
|
|
118
|
+
alternateCount: (journey.alternates || []).length,
|
|
119
|
+
reviewBoundary: reviewBoundaryForJourneyDocPolicy(journey),
|
|
120
|
+
ownership_boundary: defaultOwnershipBoundary()
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
|
|
100
124
|
/**
|
|
101
125
|
* @param {import("./types.d.ts").ContextStatement} statement
|
|
102
126
|
* @returns {any}
|
|
@@ -163,6 +187,8 @@ export function summarizeStatement(statement) {
|
|
|
163
187
|
};
|
|
164
188
|
case "widget":
|
|
165
189
|
return summarizeComponent(statement);
|
|
190
|
+
case "journey":
|
|
191
|
+
return summarizeJourney(statement);
|
|
166
192
|
case "shape":
|
|
167
193
|
return {
|
|
168
194
|
id: statement.id,
|
|
@@ -34,6 +34,7 @@ export function summarizeById(...args: any[]): any;
|
|
|
34
34
|
export function summarizeDocsByIds(...args: any[]): any;
|
|
35
35
|
export function summarizeDocument(...args: any[]): any;
|
|
36
36
|
export function summarizeDomain(...args: any[]): any;
|
|
37
|
+
export function summarizeJourneyLikeByIds(...args: any[]): any;
|
|
37
38
|
export function summarizePitch(...args: any[]): any;
|
|
38
39
|
export function summarizePlan(...args: any[]): any;
|
|
39
40
|
export function summarizeProjection(...args: any[]): any;
|
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
relatedWorkflowDocsForCapability,
|
|
21
21
|
summarizeById,
|
|
22
22
|
summarizeDocsByIds,
|
|
23
|
+
summarizeJourneyLikeByIds,
|
|
23
24
|
summarizeProjection,
|
|
24
25
|
summarizeStatementsByIds,
|
|
25
26
|
verificationIdsForTarget,
|
|
@@ -87,7 +88,7 @@ function capabilitySlice(graph, capabilityId) {
|
|
|
87
88
|
entities: summarizeStatementsByIds(graph, entities),
|
|
88
89
|
rules: summarizeStatementsByIds(graph, rules),
|
|
89
90
|
workflows: summarizeDocsByIds(graph, workflows),
|
|
90
|
-
journeys:
|
|
91
|
+
journeys: summarizeJourneyLikeByIds(graph, journeys),
|
|
91
92
|
projections: summarizeStatementsByIds(graph, projections)
|
|
92
93
|
},
|
|
93
94
|
verification: summarizeStatementsByIds(graph, verifications),
|
|
@@ -118,9 +119,12 @@ function workflowSlice(graph, workflowId) {
|
|
|
118
119
|
];
|
|
119
120
|
}))].sort();
|
|
120
121
|
const rules = [...new Set(capabilities.flatMap(/** @param {string} capabilityId */ (capabilityId) => relatedRulesForTarget(graph, capabilityId)))].sort();
|
|
121
|
-
const journeys =
|
|
122
|
-
|
|
123
|
-
.
|
|
122
|
+
const journeys = [
|
|
123
|
+
...(graph.byKind?.journey || []),
|
|
124
|
+
...(graph.docs || []).filter(/** @param {any} doc */ (doc) => doc.kind === "journey")
|
|
125
|
+
]
|
|
126
|
+
.filter(/** @param {any} journey */ (journey) => (journey.relatedWorkflows || []).includes(workflowId))
|
|
127
|
+
.map(/** @param {any} journey */ (journey) => journey.id)
|
|
124
128
|
.sort();
|
|
125
129
|
const verifications = verificationIdsForTarget(graph, [...capabilities, ...entities, workflowId]);
|
|
126
130
|
|
|
@@ -143,7 +147,7 @@ function workflowSlice(graph, workflowId) {
|
|
|
143
147
|
capabilities: summarizeStatementsByIds(graph, capabilities),
|
|
144
148
|
entities: summarizeStatementsByIds(graph, entities),
|
|
145
149
|
rules: summarizeStatementsByIds(graph, rules),
|
|
146
|
-
journeys:
|
|
150
|
+
journeys: summarizeJourneyLikeByIds(graph, journeys)
|
|
147
151
|
},
|
|
148
152
|
verification: summarizeStatementsByIds(graph, verifications),
|
|
149
153
|
verification_targets: recommendedVerificationTargets(graph, [...capabilities, ...entities, workflowId], {
|
|
@@ -49,9 +49,16 @@ import {
|
|
|
49
49
|
export function journeySlice(graph, journeyId) {
|
|
50
50
|
const journey = getJourneyDoc(graph, journeyId);
|
|
51
51
|
const capabilities = [...(journey.relatedCapabilities || [])].sort();
|
|
52
|
+
const entities = [...(journey.relatedEntities || [])].sort();
|
|
53
|
+
const rules = [...(journey.relatedRules || [])].sort();
|
|
52
54
|
const workflows = [...(journey.relatedWorkflows || [])].sort();
|
|
53
55
|
const projections = [...(journey.relatedProjections || [])].sort();
|
|
54
|
-
const
|
|
56
|
+
const widgets = [...(journey.relatedWidgets || [])].sort();
|
|
57
|
+
const declaredVerifications = [...(journey.relatedVerifications || [])].sort();
|
|
58
|
+
const verifications = [...new Set([
|
|
59
|
+
...declaredVerifications,
|
|
60
|
+
...verificationIdsForTarget(graph, [...capabilities, ...workflows, ...projections, ...widgets, journeyId])
|
|
61
|
+
])].sort();
|
|
55
62
|
|
|
56
63
|
return {
|
|
57
64
|
type: "context_slice",
|
|
@@ -63,14 +70,36 @@ export function journeySlice(graph, journeyId) {
|
|
|
63
70
|
summary: summarizeById(graph, journeyId),
|
|
64
71
|
depends_on: {
|
|
65
72
|
capabilities,
|
|
73
|
+
entities,
|
|
74
|
+
rules,
|
|
66
75
|
workflows,
|
|
67
76
|
projections,
|
|
77
|
+
widgets,
|
|
68
78
|
verifications
|
|
69
79
|
},
|
|
80
|
+
steps: (journey.steps || []).map(/** @param {any} step */ (step) => ({
|
|
81
|
+
id: step.id,
|
|
82
|
+
intent: step.intent,
|
|
83
|
+
commands: [...(step.commands || [])],
|
|
84
|
+
expects: [...(step.expects || [])],
|
|
85
|
+
after: [...(step.after || [])],
|
|
86
|
+
notes: step.notes || null
|
|
87
|
+
})),
|
|
88
|
+
alternates: (journey.alternates || []).map(/** @param {any} alternate */ (alternate) => ({
|
|
89
|
+
id: alternate.id,
|
|
90
|
+
from: alternate.from,
|
|
91
|
+
condition: alternate.condition,
|
|
92
|
+
commands: [...(alternate.commands || [])],
|
|
93
|
+
expects: [...(alternate.expects || [])],
|
|
94
|
+
notes: alternate.notes || null
|
|
95
|
+
})),
|
|
70
96
|
related: {
|
|
71
97
|
capabilities: summarizeStatementsByIds(graph, capabilities),
|
|
98
|
+
entities: summarizeStatementsByIds(graph, entities),
|
|
99
|
+
rules: summarizeStatementsByIds(graph, rules),
|
|
72
100
|
workflows: summarizeDocsByIds(graph, workflows),
|
|
73
|
-
projections: summarizeStatementsByIds(graph, projections)
|
|
101
|
+
projections: summarizeStatementsByIds(graph, projections),
|
|
102
|
+
widgets: summarizeStatementsByIds(graph, widgets)
|
|
74
103
|
},
|
|
75
104
|
verification: summarizeStatementsByIds(graph, verifications),
|
|
76
105
|
verification_targets: recommendedVerificationTargets(graph, [...capabilities, ...workflows, ...projections, journeyId], {
|
|
@@ -325,7 +325,7 @@ function importAdoptMode(graph, options = {}) {
|
|
|
325
325
|
return {
|
|
326
326
|
type: "context_task_mode",
|
|
327
327
|
version: 1,
|
|
328
|
-
mode: "
|
|
328
|
+
mode: "extract-adopt",
|
|
329
329
|
summary: {
|
|
330
330
|
focus: "Proposal review and adoption planning",
|
|
331
331
|
preferred_start: "adoption-plan.agent.json",
|
|
@@ -450,7 +450,7 @@ function verificationMode(graph, options = {}) {
|
|
|
450
450
|
export function generateContextTaskMode(graph, options = {}) {
|
|
451
451
|
const mode = String(options.modeId || "").trim();
|
|
452
452
|
if (!mode) {
|
|
453
|
-
throw new Error("context-task-mode requires --mode <modeling|maintained-app-edit|
|
|
453
|
+
throw new Error("context-task-mode requires --mode <modeling|maintained-app-edit|extract-adopt|diff-review|verification>");
|
|
454
454
|
}
|
|
455
455
|
|
|
456
456
|
if (mode === "modeling") {
|
|
@@ -459,7 +459,7 @@ export function generateContextTaskMode(graph, options = {}) {
|
|
|
459
459
|
if (mode === "maintained-app-edit") {
|
|
460
460
|
return maintainedAppEditMode(graph, options);
|
|
461
461
|
}
|
|
462
|
-
if (mode === "
|
|
462
|
+
if (mode === "extract-adopt") {
|
|
463
463
|
return importAdoptMode(graph, options);
|
|
464
464
|
}
|
|
465
465
|
if (mode === "diff-review") {
|
|
@@ -17,12 +17,12 @@ export function reportMarkdown(track, candidates) {
|
|
|
17
17
|
` - manual next: ${(seam.manual_next_steps || []).slice(0, 2).join(" ")}`
|
|
18
18
|
]);
|
|
19
19
|
return ensureTrailingNewline(
|
|
20
|
-
`# DB
|
|
20
|
+
`# DB Extract Report\n\n- Entities: ${candidates.entities.length}\n- Enums: ${candidates.enums.length}\n- Relations: ${candidates.relations.length}\n- Indexes: ${candidates.indexes.length}\n- Maintained DB migration seams: ${(candidates.maintained_seams || []).length}\n\n## Maintained DB Migration Seam Candidates\n\n${seamLines.length ? seamLines.join("\n") : "- none"}\n`
|
|
21
21
|
);
|
|
22
22
|
}
|
|
23
23
|
if (track === "api") {
|
|
24
24
|
return ensureTrailingNewline(
|
|
25
|
-
`# API
|
|
25
|
+
`# API Extract Report\n\n- Capabilities: ${candidates.capabilities.length}\n- Routes: ${candidates.routes.length}\n- Stacks: ${candidates.stacks.length ? candidates.stacks.join(", ") : "none"}\n`
|
|
26
26
|
);
|
|
27
27
|
}
|
|
28
28
|
if (track === "ui") {
|
|
@@ -36,7 +36,7 @@ export function reportMarkdown(track, candidates) {
|
|
|
36
36
|
`- \`${flow.id_hint}\` type \`${flow.flow_type}\` confidence ${flow.confidence || "unknown"} routes ${(flow.route_paths || []).map((/** @type {string} */ route) => `\`${route}\``).join(", ") || "_none_"} missing decisions ${(flow.missing_decisions || []).length}`
|
|
37
37
|
);
|
|
38
38
|
return ensureTrailingNewline(
|
|
39
|
-
`# UI
|
|
39
|
+
`# UI Extract Report\n\n- Screens: ${candidates.screens.length}\n- Routes: ${candidates.routes.length}\n- Actions: ${candidates.actions.length}\n- Flow candidates: ${flows.length}\n- Widgets: ${widgets.length}\n- Event payload shapes: ${shapes.length}\n- Stacks: ${candidates.stacks.length ? candidates.stacks.join(", ") : "none"}\n\n## Flow Candidates\n\n${flowLines.length ? flowLines.join("\n") : "- none"}\n\n## Widget Candidates\n\n${widgetLines.length ? widgetLines.join("\n") : "- none"}\n\n## Next Validation\n\n- Review flow candidates in \`topo/candidates/app/ui/candidates.json\` before adding shared UI contract behavior.\n- Review candidates under \`topo/candidates/app/ui/drafts/widgets/**\`.\n- Run \`topogram extract plan <path>\` before adoption.\n- After adoption, run \`topogram check <path>\`, \`topogram widget check <path>\`, and \`topogram widget behavior <path>\`.\n`
|
|
40
40
|
);
|
|
41
41
|
}
|
|
42
42
|
if (track === "cli") {
|
|
@@ -44,7 +44,7 @@ export function reportMarkdown(track, candidates) {
|
|
|
44
44
|
`- \`${command.command_id || command.id_hint}\` usage \`${command.usage || "unknown"}\` effects ${(command.effects || []).map((/** @type {any} */ effect) => `\`${effect}\``).join(", ") || "_none_"}`
|
|
45
45
|
);
|
|
46
46
|
return ensureTrailingNewline(
|
|
47
|
-
`# CLI
|
|
47
|
+
`# CLI Extract Report\n\n- Commands: ${candidates.commands.length}\n- Capabilities: ${candidates.capabilities.length}\n- CLI surfaces: ${candidates.surfaces.length}\n\n## Command Candidates\n\n${commandLines.length ? commandLines.join("\n") : "- none"}\n`
|
|
48
48
|
);
|
|
49
49
|
}
|
|
50
50
|
if (track === "verification") {
|
|
@@ -107,7 +107,7 @@ export function classifyImportSourcePath(paths, filePath) {
|
|
|
107
107
|
if (/(^|\/)(\.tmp|tmp|temp|dist|build|coverage|out|generated|docs-generated|snapshots?)(\/|$)/i.test(relativePath)) {
|
|
108
108
|
return "generated_output";
|
|
109
109
|
}
|
|
110
|
-
if (
|
|
110
|
+
if (/^(template|templates|[^/]+-templates|[^/]+_templates)(\/|$)/i.test(relativePath)) {
|
|
111
111
|
return "fixtures";
|
|
112
112
|
}
|
|
113
113
|
if (/(^|\/)(test|tests|__tests__|spec|specs|mocks?)(\/|$)|\.(test|spec)\.(js|jsx|ts|tsx|mjs|cjs)$/i.test(relativePath)) {
|
|
@@ -213,9 +213,10 @@ export function selectPreferredImportFiles(paths, files, kind) {
|
|
|
213
213
|
/**
|
|
214
214
|
* @param {import("./types.d.ts").ImportPaths} paths
|
|
215
215
|
* @param {any} predicate
|
|
216
|
+
* @param {{ primaryOnly?: boolean }} [options]
|
|
216
217
|
* @returns {any}
|
|
217
218
|
*/
|
|
218
|
-
export function findImportFiles(paths, predicate) {
|
|
219
|
+
export function findImportFiles(paths, predicate, options = {}) {
|
|
219
220
|
const files = new Set();
|
|
220
221
|
for (const rootDir of importSearchRoots(paths)) {
|
|
221
222
|
for (const filePath of listFilesRecursive(rootDir, predicate)) {
|
|
@@ -226,8 +227,26 @@ export function findImportFiles(paths, predicate) {
|
|
|
226
227
|
) {
|
|
227
228
|
continue;
|
|
228
229
|
}
|
|
230
|
+
if (options.primaryOnly && !isPrimaryImportSource(paths, filePath)) {
|
|
231
|
+
continue;
|
|
232
|
+
}
|
|
229
233
|
files.add(filePath);
|
|
230
234
|
}
|
|
231
235
|
}
|
|
232
236
|
return [...files].sort();
|
|
233
237
|
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Find files that are eligible to create primary import candidates.
|
|
241
|
+
*
|
|
242
|
+
* Docs, tests, fixture/template roots, generated output, and cache output may
|
|
243
|
+
* still support evidence elsewhere, but candidate-producing extractors should
|
|
244
|
+
* start from this helper so the primary-source rule is one API boundary.
|
|
245
|
+
*
|
|
246
|
+
* @param {import("./types.d.ts").ImportPaths} paths
|
|
247
|
+
* @param {any} predicate
|
|
248
|
+
* @returns {any}
|
|
249
|
+
*/
|
|
250
|
+
export function findPrimaryImportFiles(paths, predicate) {
|
|
251
|
+
return findImportFiles(paths, predicate, { primaryOnly: true });
|
|
252
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { findPrimaryImportFiles, readTextIfExists } from "../core/shared.js";
|
|
4
4
|
|
|
5
5
|
function splitClassBlocks(text) {
|
|
6
6
|
const lines = String(text || "").split(/\r?\n/);
|
|
@@ -51,7 +51,7 @@ function splitClassBlocks(text) {
|
|
|
51
51
|
}
|
|
52
52
|
|
|
53
53
|
function buildSerializerIndex(paths) {
|
|
54
|
-
const files =
|
|
54
|
+
const files = findPrimaryImportFiles(paths, (filePath) => /\/serializers\.py$/i.test(filePath));
|
|
55
55
|
const index = new Map();
|
|
56
56
|
|
|
57
57
|
for (const filePath of files) {
|
|
@@ -89,7 +89,7 @@ function buildSerializerIndex(paths) {
|
|
|
89
89
|
}
|
|
90
90
|
|
|
91
91
|
function buildViewIndex(paths) {
|
|
92
|
-
const files =
|
|
92
|
+
const files = findPrimaryImportFiles(paths, (filePath) => /\/views\.py$/i.test(filePath));
|
|
93
93
|
const index = new Map();
|
|
94
94
|
|
|
95
95
|
for (const filePath of files) {
|
|
@@ -182,7 +182,7 @@ export const djangoRestEnricher = {
|
|
|
182
182
|
applies(context, candidates) {
|
|
183
183
|
if ((candidates.capabilities || []).length === 0) return false;
|
|
184
184
|
return (candidates.stacks || []).includes("django") ||
|
|
185
|
-
|
|
185
|
+
findPrimaryImportFiles(context.paths, (filePath) => /\/serializers\.py$/i.test(filePath)).length > 0;
|
|
186
186
|
},
|
|
187
187
|
enrich(context, candidates) {
|
|
188
188
|
const serializerIndex = buildSerializerIndex(context.paths);
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { findPrimaryImportFiles, readTextIfExists } from "../core/shared.js";
|
|
4
4
|
|
|
5
5
|
function buildControllerIndex(paths) {
|
|
6
|
-
const files =
|
|
6
|
+
const files = findPrimaryImportFiles(paths, (filePath) => /app\/controllers\/.+_controller\.rb$/i.test(filePath));
|
|
7
7
|
const index = new Map();
|
|
8
8
|
for (const filePath of files) {
|
|
9
9
|
const text = readTextIfExists(filePath) || "";
|
|
@@ -186,7 +186,7 @@ export const railsControllerEnricher = {
|
|
|
186
186
|
applies(context, candidates) {
|
|
187
187
|
if ((candidates.capabilities || []).length === 0) return false;
|
|
188
188
|
return (candidates.stacks || []).includes("rails") ||
|
|
189
|
-
|
|
189
|
+
findPrimaryImportFiles(context.paths, (filePath) => /app\/controllers\/.+_controller\.rb$/i.test(filePath)).length > 0;
|
|
190
190
|
},
|
|
191
191
|
enrich(context, candidates) {
|
|
192
192
|
const controllers = buildControllerIndex(context.paths);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { findPrimaryImportFiles, readTextIfExists } from "../core/shared.js";
|
|
4
4
|
|
|
5
5
|
function modelClassNameForEntity(entityId) {
|
|
6
6
|
const stem = String(entityId || "")
|
|
@@ -13,7 +13,7 @@ function modelClassNameForEntity(entityId) {
|
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
function buildModelIndex(paths) {
|
|
16
|
-
const modelFiles =
|
|
16
|
+
const modelFiles = findPrimaryImportFiles(paths, (filePath) => /app\/models\/.+\.rb$/i.test(filePath));
|
|
17
17
|
const index = new Map();
|
|
18
18
|
for (const filePath of modelFiles) {
|
|
19
19
|
const text = readTextIfExists(filePath) || "";
|
|
@@ -93,7 +93,7 @@ export const railsModelEnricher = {
|
|
|
93
93
|
track: "db",
|
|
94
94
|
applies(context, candidates) {
|
|
95
95
|
if ((candidates.entities || []).length === 0) return false;
|
|
96
|
-
return
|
|
96
|
+
return findPrimaryImportFiles(context.paths, (filePath) => /app\/models\/.+\.rb$/i.test(filePath)).length > 0;
|
|
97
97
|
},
|
|
98
98
|
enrich(context, candidates) {
|
|
99
99
|
const modelIndex = buildModelIndex(context.paths);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
dedupeCandidateRecords,
|
|
3
|
-
|
|
3
|
+
findPrimaryImportFiles,
|
|
4
4
|
inferApiEntityIdFromPath,
|
|
5
5
|
inferRouteCapabilityId,
|
|
6
6
|
makeCandidateRecord,
|
|
@@ -22,7 +22,7 @@ function parsePublicProperties(text) {
|
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
function buildDotnetFileIndex(paths) {
|
|
25
|
-
const files =
|
|
25
|
+
const files = findPrimaryImportFiles(paths, (filePath) => /\.cs$/i.test(filePath));
|
|
26
26
|
const index = new Map();
|
|
27
27
|
for (const filePath of files) {
|
|
28
28
|
index.set(relativeTo(paths.repoRoot, filePath), readTextIfExists(filePath) || "");
|
|
@@ -239,8 +239,8 @@ export const aspNetCoreExtractor = {
|
|
|
239
239
|
id: "api.aspnet-core",
|
|
240
240
|
track: "api",
|
|
241
241
|
detect(context) {
|
|
242
|
-
const controllerFiles =
|
|
243
|
-
const programFiles =
|
|
242
|
+
const controllerFiles = findPrimaryImportFiles(context.paths, (filePath) => /Controller\.cs$/i.test(filePath));
|
|
243
|
+
const programFiles = findPrimaryImportFiles(context.paths, (filePath) => /Program\.cs$/i.test(filePath));
|
|
244
244
|
const score = controllerFiles.length > 0 && programFiles.some((filePath) => /WebApplication\.CreateBuilder|AddSwaggerGen|AddMvc/.test(readTextIfExists(filePath) || "")) ? 88 : 0;
|
|
245
245
|
return {
|
|
246
246
|
score,
|
|
@@ -248,7 +248,7 @@ export const aspNetCoreExtractor = {
|
|
|
248
248
|
};
|
|
249
249
|
},
|
|
250
250
|
extract(context) {
|
|
251
|
-
const controllerFiles =
|
|
251
|
+
const controllerFiles = findPrimaryImportFiles(context.paths, (filePath) => /Controller\.cs$/i.test(filePath));
|
|
252
252
|
const featureFiles = buildDotnetFileIndex(context.paths).files.map((filePath) => ({ filePath, text: readTextIfExists(filePath) || "" }));
|
|
253
253
|
const findings = [];
|
|
254
254
|
const candidates = { capabilities: [], routes: [], stacks: [] };
|
|
@@ -2,7 +2,7 @@ import path from "node:path";
|
|
|
2
2
|
|
|
3
3
|
import {
|
|
4
4
|
dedupeCandidateRecords,
|
|
5
|
-
|
|
5
|
+
findPrimaryImportFiles,
|
|
6
6
|
inferApiEntityIdFromPath,
|
|
7
7
|
inferRouteCapabilityId,
|
|
8
8
|
makeCandidateRecord,
|
|
@@ -90,7 +90,7 @@ function permissionAuthHint(permissionText, method) {
|
|
|
90
90
|
}
|
|
91
91
|
|
|
92
92
|
function buildViewIndex(paths) {
|
|
93
|
-
const viewFiles =
|
|
93
|
+
const viewFiles = findPrimaryImportFiles(paths, (filePath) => /\/views\.py$/i.test(filePath));
|
|
94
94
|
const index = new Map();
|
|
95
95
|
|
|
96
96
|
for (const filePath of viewFiles) {
|
|
@@ -250,8 +250,8 @@ export const djangoRoutesExtractor = {
|
|
|
250
250
|
id: "api.django-routes",
|
|
251
251
|
track: "api",
|
|
252
252
|
detect(context) {
|
|
253
|
-
const manageFiles =
|
|
254
|
-
const urlFiles =
|
|
253
|
+
const manageFiles = findPrimaryImportFiles(context.paths, (filePath) => /\/manage\.py$/i.test(filePath));
|
|
254
|
+
const urlFiles = findPrimaryImportFiles(context.paths, (filePath) => /\/urls\.py$/i.test(filePath));
|
|
255
255
|
const score = manageFiles.length > 0 && urlFiles.length > 0 ? 90 : 0;
|
|
256
256
|
return {
|
|
257
257
|
score,
|
|
@@ -259,7 +259,7 @@ export const djangoRoutesExtractor = {
|
|
|
259
259
|
};
|
|
260
260
|
},
|
|
261
261
|
extract(context) {
|
|
262
|
-
const urlFiles =
|
|
262
|
+
const urlFiles = findPrimaryImportFiles(context.paths, (filePath) => /\/urls\.py$/i.test(filePath));
|
|
263
263
|
const moduleMap = new Map(urlFiles.map((filePath) => [moduleNameForFile(context.paths.repoRoot, filePath), filePath]));
|
|
264
264
|
const viewIndex = buildViewIndex(context.paths);
|
|
265
265
|
const rootFiles = urlFiles.filter((filePath) => !/\/apps\//.test(filePath));
|
|
@@ -2,7 +2,7 @@ import path from "node:path";
|
|
|
2
2
|
|
|
3
3
|
import {
|
|
4
4
|
dedupeCandidateRecords,
|
|
5
|
-
|
|
5
|
+
findPrimaryImportFiles,
|
|
6
6
|
inferApiEntityIdFromPath,
|
|
7
7
|
inferRouteAuthHint,
|
|
8
8
|
inferRouteCapabilityId,
|
|
@@ -94,18 +94,18 @@ export const expressExtractor = {
|
|
|
94
94
|
id: "api.express",
|
|
95
95
|
track: "api",
|
|
96
96
|
detect(context) {
|
|
97
|
-
const routeFiles =
|
|
97
|
+
const routeFiles = findPrimaryImportFiles(context.paths, (filePath) => /src\/routes\/.+\.(ts|js|mjs|cjs)$/i.test(filePath));
|
|
98
98
|
return {
|
|
99
99
|
score: routeFiles.length > 0 ? 85 : 0,
|
|
100
100
|
reasons: routeFiles.length > 0 ? ["Found Express route modules"] : []
|
|
101
101
|
};
|
|
102
102
|
},
|
|
103
103
|
extract(context) {
|
|
104
|
-
const permissionsFile =
|
|
104
|
+
const permissionsFile = findPrimaryImportFiles(context.paths, (filePath) => /src\/helpers\/permissions\.(ts|js|mjs|cjs)$/i.test(filePath))[0];
|
|
105
105
|
const permissionsText = permissionsFile ? context.helpers.readTextIfExists(permissionsFile) || "" : "";
|
|
106
106
|
const apiRoutes = parseApiRoutesMap(permissionsText);
|
|
107
107
|
const permissionMeta = parsePermissionsMetadata(permissionsText);
|
|
108
|
-
const routeFiles =
|
|
108
|
+
const routeFiles = findPrimaryImportFiles(context.paths, (filePath) => /src\/routes\/.+\.(ts|js|mjs|cjs)$/i.test(filePath));
|
|
109
109
|
const routes = routeFiles.flatMap((filePath) =>
|
|
110
110
|
parseExpressRouteCalls(filePath, context.helpers.readTextIfExists(filePath) || "", apiRoutes, permissionMeta)
|
|
111
111
|
);
|
|
@@ -2,7 +2,7 @@ import path from "node:path";
|
|
|
2
2
|
|
|
3
3
|
import {
|
|
4
4
|
dedupeCandidateRecords,
|
|
5
|
-
|
|
5
|
+
findPrimaryImportFiles,
|
|
6
6
|
inferApiCapabilityIdFromOperation,
|
|
7
7
|
inferApiEntityIdFromPath,
|
|
8
8
|
makeCandidateRecord,
|
|
@@ -263,8 +263,8 @@ export const fastifyExtractor = {
|
|
|
263
263
|
id: "api.fastify",
|
|
264
264
|
track: "api",
|
|
265
265
|
detect(context) {
|
|
266
|
-
const routeFiles =
|
|
267
|
-
const packageJsonFiles =
|
|
266
|
+
const routeFiles = findPrimaryImportFiles(context.paths, (filePath) => /src\/routes\/api\/.+\.(ts|js|mjs|cjs)$/i.test(filePath));
|
|
267
|
+
const packageJsonFiles = findPrimaryImportFiles(context.paths, (filePath) => /package\.json$/i.test(filePath));
|
|
268
268
|
const hasFastifyDependency = packageJsonFiles.some((filePath) => /"fastify"\s*:/.test(context.helpers.readTextIfExists(filePath) || ""));
|
|
269
269
|
return {
|
|
270
270
|
score: routeFiles.length > 0 && hasFastifyDependency ? 86 : 0,
|
|
@@ -272,16 +272,16 @@ export const fastifyExtractor = {
|
|
|
272
272
|
};
|
|
273
273
|
},
|
|
274
274
|
extract(context) {
|
|
275
|
-
const apiRoutesRoot =
|
|
276
|
-
? path.join(path.dirname(
|
|
275
|
+
const apiRoutesRoot = findPrimaryImportFiles(context.paths, (filePath) => /src\/routes\/api\/index\.(ts|js|mjs|cjs)$/i.test(filePath))[0]
|
|
276
|
+
? path.join(path.dirname(findPrimaryImportFiles(context.paths, (filePath) => /src\/routes\/api\/index\.(ts|js|mjs|cjs)$/i.test(filePath))[0]))
|
|
277
277
|
: null;
|
|
278
278
|
if (!apiRoutesRoot) {
|
|
279
279
|
return { findings: [], candidates: { capabilities: [], routes: [], stacks: [] } };
|
|
280
280
|
}
|
|
281
281
|
|
|
282
|
-
const routeFiles =
|
|
282
|
+
const routeFiles = findPrimaryImportFiles(context.paths, (filePath) => /src\/routes\/api\/.+\.(ts|js|mjs|cjs)$/i.test(filePath))
|
|
283
283
|
.filter((filePath) => !/\/autohooks\.(ts|js|mjs|cjs)$/i.test(filePath));
|
|
284
|
-
const schemaFiles =
|
|
284
|
+
const schemaFiles = findPrimaryImportFiles(context.paths, (filePath) => /src\/schemas\/.+\.(ts|js|mjs|cjs)$/i.test(filePath));
|
|
285
285
|
const namedSchemas = parseNamedTypeboxSchemas(schemaFiles, context.helpers.readTextIfExists);
|
|
286
286
|
|
|
287
287
|
const routes = [];
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
canonicalCandidateTerm,
|
|
3
3
|
dedupeCandidateRecords,
|
|
4
|
-
|
|
4
|
+
findPrimaryImportFiles,
|
|
5
5
|
makeCandidateRecord,
|
|
6
6
|
pluralizeCandidateTerm,
|
|
7
7
|
relativeTo,
|
|
@@ -31,7 +31,7 @@ function capabilityIdFor(featureStem, methodName, httpMethod) {
|
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
function extractApiConfigPaths(context) {
|
|
34
|
-
const configFile =
|
|
34
|
+
const configFile = findPrimaryImportFiles(context.paths, (filePath) => /\/lib\/common\/network\/api_config\.dart$/i.test(filePath))[0];
|
|
35
35
|
const mapping = new Map();
|
|
36
36
|
if (!configFile) return mapping;
|
|
37
37
|
const text = context.helpers.readTextIfExists(configFile) || "";
|
|
@@ -87,7 +87,7 @@ export const flutterDioExtractor = {
|
|
|
87
87
|
id: "api.flutter-dio",
|
|
88
88
|
track: "api",
|
|
89
89
|
detect(context) {
|
|
90
|
-
const files =
|
|
90
|
+
const files = findPrimaryImportFiles(
|
|
91
91
|
context.paths,
|
|
92
92
|
(filePath) => /\/lib\/features\/.+\/data\/datasources\/.+_remote_data_source\.dart$/i.test(filePath)
|
|
93
93
|
);
|
|
@@ -98,7 +98,7 @@ export const flutterDioExtractor = {
|
|
|
98
98
|
};
|
|
99
99
|
},
|
|
100
100
|
extract(context) {
|
|
101
|
-
const files =
|
|
101
|
+
const files = findPrimaryImportFiles(
|
|
102
102
|
context.paths,
|
|
103
103
|
(filePath) => /\/lib\/features\/.+\/data\/datasources\/.+_remote_data_source\.dart$/i.test(filePath)
|
|
104
104
|
);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { findPrimaryImportFiles, inferRouteAuthHint, inferRouteCapabilityId, inferRouteQueryParams, isPrimaryImportSource, makeCandidateRecord, normalizeOpenApiPath, relativeTo } from "../../core/shared.js";
|
|
2
2
|
|
|
3
3
|
function extractHandlerContext(text, handlerName) {
|
|
4
4
|
if (!handlerName) return "";
|
|
@@ -15,7 +15,7 @@ function extractHandlerContext(text, handlerName) {
|
|
|
15
15
|
|
|
16
16
|
function inferServerRoutes(context) {
|
|
17
17
|
const routes = [];
|
|
18
|
-
const routeFiles =
|
|
18
|
+
const routeFiles = findPrimaryImportFiles(
|
|
19
19
|
context.paths,
|
|
20
20
|
(filePath) =>
|
|
21
21
|
/\.(ts|tsx|js|jsx|mjs|cjs)$/.test(filePath) &&
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
canonicalCandidateTerm,
|
|
3
3
|
dedupeCandidateRecords,
|
|
4
|
-
|
|
4
|
+
findPrimaryImportFiles,
|
|
5
5
|
idHintify,
|
|
6
6
|
makeCandidateRecord,
|
|
7
7
|
pluralizeCandidateTerm,
|
|
@@ -464,7 +464,7 @@ export const graphQlCodeFirstExtractor = {
|
|
|
464
464
|
id: "api.graphql-code-first",
|
|
465
465
|
track: "api",
|
|
466
466
|
detect(context) {
|
|
467
|
-
const files =
|
|
467
|
+
const files = findPrimaryImportFiles(context.paths, (filePath) => /(\/src\/.+|\/pages\/api\/.+)\.(ts|tsx|js|jsx)$/i.test(filePath));
|
|
468
468
|
const hasNestResolvers = files.some((filePath) => {
|
|
469
469
|
const text = readTextIfExists(filePath) || "";
|
|
470
470
|
return /@nestjs\/graphql/.test(text) && /@(Query|Mutation)\s*\(/.test(text);
|
|
@@ -489,7 +489,7 @@ export const graphQlCodeFirstExtractor = {
|
|
|
489
489
|
};
|
|
490
490
|
},
|
|
491
491
|
extract(context) {
|
|
492
|
-
const files =
|
|
492
|
+
const files = findPrimaryImportFiles(context.paths, (filePath) => /(\/src\/.+|\/pages\/api\/.+)\.(ts|tsx|js|jsx)$/i.test(filePath)).filter((filePath) => !/\.test\./i.test(filePath));
|
|
493
493
|
const nestTypes = parseNestTypes(files);
|
|
494
494
|
const pothosTypes = parsePothosTypes(files);
|
|
495
495
|
const nexusTypes = parseNexusTypes(files);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
canonicalCandidateTerm,
|
|
3
3
|
dedupeCandidateRecords,
|
|
4
|
-
|
|
4
|
+
findPrimaryImportFiles,
|
|
5
5
|
idHintify,
|
|
6
6
|
makeCandidateRecord,
|
|
7
7
|
pluralizeCandidateTerm,
|
|
@@ -174,7 +174,7 @@ function inferEndpointPath(context, graphqlFiles) {
|
|
|
174
174
|
return endpointMatch[1];
|
|
175
175
|
}
|
|
176
176
|
}
|
|
177
|
-
const packageJsonPath =
|
|
177
|
+
const packageJsonPath = findPrimaryImportFiles(context.paths, (filePath) => /package\.json$/i.test(filePath))[0];
|
|
178
178
|
const packageText = packageJsonPath ? readTextIfExists(packageJsonPath) : null;
|
|
179
179
|
if (packageText && /graphql-yoga|apollo-server|@apollo\/server/.test(packageText)) {
|
|
180
180
|
return "/graphql";
|
|
@@ -183,7 +183,7 @@ function inferEndpointPath(context, graphqlFiles) {
|
|
|
183
183
|
}
|
|
184
184
|
|
|
185
185
|
function extractGraphqlSchemaSources(context) {
|
|
186
|
-
const files =
|
|
186
|
+
const files = findPrimaryImportFiles(
|
|
187
187
|
context.paths,
|
|
188
188
|
(filePath) =>
|
|
189
189
|
/\/src\/.+\.(ts|tsx|js|jsx)$/i.test(filePath) ||
|
|
@@ -210,7 +210,7 @@ export const graphQlSdlExtractor = {
|
|
|
210
210
|
detect(context) {
|
|
211
211
|
const schemaSources = extractGraphqlSchemaSources(context);
|
|
212
212
|
const hasOperations = schemaSources.some(({ schema }) => /\btype\s+Query\b|\btype\s+Mutation\b/.test(schema));
|
|
213
|
-
const packageJsonPath =
|
|
213
|
+
const packageJsonPath = findPrimaryImportFiles(context.paths, (filePath) => /package\.json$/i.test(filePath))[0];
|
|
214
214
|
const packageText = packageJsonPath ? readTextIfExists(packageJsonPath) : "";
|
|
215
215
|
const hasGraphqlRuntime = /graphql-yoga|graphql|apollo-server|@apollo\/server/.test(packageText || "");
|
|
216
216
|
return {
|
|
@@ -220,7 +220,7 @@ export const graphQlSdlExtractor = {
|
|
|
220
220
|
},
|
|
221
221
|
extract(context) {
|
|
222
222
|
const schemaSources = extractGraphqlSchemaSources(context);
|
|
223
|
-
const endpointPath = inferEndpointPath(context,
|
|
223
|
+
const endpointPath = inferEndpointPath(context, findPrimaryImportFiles(context.paths, (filePath) => /\/src\/.+\.(ts|tsx|js|jsx)$/i.test(filePath)));
|
|
224
224
|
const mergedSchema = schemaSources.map(({ schema }) => schema).join("\n\n");
|
|
225
225
|
const blocks = parseGraphqlSchemaBlocks(mergedSchema);
|
|
226
226
|
const inputTypes = new Map([...blocks.entries()].filter(([, block]) => block.kind === "input"));
|