@mcoda/core 0.1.18 → 0.1.20
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/api/QaTasksApi.d.ts.map +1 -1
- package/dist/api/QaTasksApi.js +3 -0
- package/dist/prompts/PdrPrompts.d.ts.map +1 -1
- package/dist/prompts/PdrPrompts.js +22 -8
- package/dist/prompts/SdsPrompts.d.ts.map +1 -1
- package/dist/prompts/SdsPrompts.js +53 -34
- package/dist/services/backlog/BacklogService.d.ts.map +1 -1
- package/dist/services/backlog/BacklogService.js +3 -0
- package/dist/services/backlog/TaskOrderingService.d.ts +9 -0
- package/dist/services/backlog/TaskOrderingService.d.ts.map +1 -1
- package/dist/services/backlog/TaskOrderingService.js +251 -35
- package/dist/services/docs/DocsService.d.ts.map +1 -1
- package/dist/services/docs/DocsService.js +487 -71
- package/dist/services/docs/review/gates/PdrFolderTreeGate.d.ts +7 -0
- package/dist/services/docs/review/gates/PdrFolderTreeGate.d.ts.map +1 -0
- package/dist/services/docs/review/gates/PdrFolderTreeGate.js +151 -0
- package/dist/services/docs/review/gates/PdrNoUnresolvedItemsGate.d.ts +7 -0
- package/dist/services/docs/review/gates/PdrNoUnresolvedItemsGate.d.ts.map +1 -0
- package/dist/services/docs/review/gates/PdrNoUnresolvedItemsGate.js +109 -0
- package/dist/services/docs/review/gates/PdrTechStackRationaleGate.d.ts +7 -0
- package/dist/services/docs/review/gates/PdrTechStackRationaleGate.d.ts.map +1 -0
- package/dist/services/docs/review/gates/PdrTechStackRationaleGate.js +128 -0
- package/dist/services/docs/review/gates/SdsFolderTreeGate.d.ts +7 -0
- package/dist/services/docs/review/gates/SdsFolderTreeGate.d.ts.map +1 -0
- package/dist/services/docs/review/gates/SdsFolderTreeGate.js +153 -0
- package/dist/services/docs/review/gates/SdsNoUnresolvedItemsGate.d.ts +7 -0
- package/dist/services/docs/review/gates/SdsNoUnresolvedItemsGate.d.ts.map +1 -0
- package/dist/services/docs/review/gates/SdsNoUnresolvedItemsGate.js +109 -0
- package/dist/services/docs/review/gates/SdsTechStackRationaleGate.d.ts +7 -0
- package/dist/services/docs/review/gates/SdsTechStackRationaleGate.d.ts.map +1 -0
- package/dist/services/docs/review/gates/SdsTechStackRationaleGate.js +128 -0
- package/dist/services/estimate/EstimateService.d.ts +2 -0
- package/dist/services/estimate/EstimateService.d.ts.map +1 -1
- package/dist/services/estimate/EstimateService.js +54 -0
- package/dist/services/execution/QaTasksService.d.ts +6 -0
- package/dist/services/execution/QaTasksService.d.ts.map +1 -1
- package/dist/services/execution/QaTasksService.js +278 -95
- package/dist/services/execution/TaskSelectionService.d.ts +3 -0
- package/dist/services/execution/TaskSelectionService.d.ts.map +1 -1
- package/dist/services/execution/TaskSelectionService.js +33 -0
- package/dist/services/execution/WorkOnTasksService.d.ts +4 -0
- package/dist/services/execution/WorkOnTasksService.d.ts.map +1 -1
- package/dist/services/execution/WorkOnTasksService.js +146 -22
- package/dist/services/openapi/OpenApiService.d.ts.map +1 -1
- package/dist/services/openapi/OpenApiService.js +43 -4
- package/dist/services/planning/CreateTasksService.d.ts +15 -0
- package/dist/services/planning/CreateTasksService.d.ts.map +1 -1
- package/dist/services/planning/CreateTasksService.js +592 -81
- package/dist/services/planning/RefineTasksService.d.ts +1 -0
- package/dist/services/planning/RefineTasksService.d.ts.map +1 -1
- package/dist/services/planning/RefineTasksService.js +88 -2
- package/dist/services/review/CodeReviewService.d.ts +6 -0
- package/dist/services/review/CodeReviewService.d.ts.map +1 -1
- package/dist/services/review/CodeReviewService.js +260 -41
- package/dist/services/shared/ProjectGuidance.d.ts +18 -2
- package/dist/services/shared/ProjectGuidance.d.ts.map +1 -1
- package/dist/services/shared/ProjectGuidance.js +535 -34
- package/package.json +6 -6
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { DocgenArtifactInventory } from "../../DocgenRunContext.js";
|
|
2
|
+
import { ReviewGateResult } from "../ReviewTypes.js";
|
|
3
|
+
export interface PdrFolderTreeGateInput {
|
|
4
|
+
artifacts: DocgenArtifactInventory;
|
|
5
|
+
}
|
|
6
|
+
export declare const runPdrFolderTreeGate: (input: PdrFolderTreeGateInput) => Promise<ReviewGateResult>;
|
|
7
|
+
//# sourceMappingURL=PdrFolderTreeGate.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PdrFolderTreeGate.d.ts","sourceRoot":"","sources":["../../../../../src/services/docs/review/gates/PdrFolderTreeGate.ts"],"names":[],"mappings":"AACA,OAAO,EAAqB,uBAAuB,EAAE,MAAM,2BAA2B,CAAC;AACvF,OAAO,EAAE,gBAAgB,EAAe,MAAM,mBAAmB,CAAC;AAElE,MAAM,WAAW,sBAAsB;IACrC,SAAS,EAAE,uBAAuB,CAAC;CACpC;AAqFD,eAAO,MAAM,oBAAoB,GAC/B,OAAO,sBAAsB,KAC5B,OAAO,CAAC,gBAAgB,CAwF1B,CAAC"}
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import { promises as fs } from "node:fs";
|
|
2
|
+
const FOLDER_TREE_HEADING = /folder tree|directory structure|repository structure|target structure/i;
|
|
3
|
+
const TREE_ENTRY_MINIMUM = 8;
|
|
4
|
+
const isFenceLine = (line) => /^```|^~~~/.test(line.trim());
|
|
5
|
+
const extractSection = (lines, headingMatch) => {
|
|
6
|
+
let inFence = false;
|
|
7
|
+
let capture = false;
|
|
8
|
+
let startLine = 0;
|
|
9
|
+
const collected = [];
|
|
10
|
+
for (let i = 0; i < lines.length; i += 1) {
|
|
11
|
+
const line = lines[i] ?? "";
|
|
12
|
+
const trimmed = line.trim();
|
|
13
|
+
if (isFenceLine(trimmed)) {
|
|
14
|
+
inFence = !inFence;
|
|
15
|
+
if (capture)
|
|
16
|
+
collected.push(trimmed);
|
|
17
|
+
continue;
|
|
18
|
+
}
|
|
19
|
+
const heading = trimmed.match(/^#{1,6}\s+(.*)$/);
|
|
20
|
+
if (heading && !inFence) {
|
|
21
|
+
const title = heading[1]?.trim() ?? "";
|
|
22
|
+
if (headingMatch.test(title)) {
|
|
23
|
+
capture = true;
|
|
24
|
+
startLine = i + 1;
|
|
25
|
+
continue;
|
|
26
|
+
}
|
|
27
|
+
if (capture)
|
|
28
|
+
break;
|
|
29
|
+
}
|
|
30
|
+
if (capture)
|
|
31
|
+
collected.push(line);
|
|
32
|
+
}
|
|
33
|
+
if (!capture)
|
|
34
|
+
return undefined;
|
|
35
|
+
return { content: collected, line: startLine };
|
|
36
|
+
};
|
|
37
|
+
const extractTreeBlock = (content) => {
|
|
38
|
+
const match = content.match(/```(?:text)?\s*([\s\S]*?)```/i);
|
|
39
|
+
if (!match)
|
|
40
|
+
return undefined;
|
|
41
|
+
return match[1]?.trim();
|
|
42
|
+
};
|
|
43
|
+
const countTreeEntries = (treeBlock) => treeBlock
|
|
44
|
+
.split(/\r?\n/)
|
|
45
|
+
.map((line) => line.trim())
|
|
46
|
+
.filter((line) => {
|
|
47
|
+
if (!line)
|
|
48
|
+
return false;
|
|
49
|
+
if (line === ".")
|
|
50
|
+
return true;
|
|
51
|
+
if (/^[├└│]/.test(line))
|
|
52
|
+
return true;
|
|
53
|
+
if (/[A-Za-z0-9_.-]+\/?(\s+[#-].*)?$/.test(line))
|
|
54
|
+
return true;
|
|
55
|
+
return false;
|
|
56
|
+
}).length;
|
|
57
|
+
const hasResponsibilityHints = (treeBlock) => /#|responsibilit|owner|module|service|tests?|scripts?/i.test(treeBlock);
|
|
58
|
+
const buildIssue = (input) => ({
|
|
59
|
+
id: input.id,
|
|
60
|
+
gateId: "gate-pdr-folder-tree",
|
|
61
|
+
severity: "high",
|
|
62
|
+
category: "completeness",
|
|
63
|
+
artifact: "pdr",
|
|
64
|
+
message: input.message,
|
|
65
|
+
remediation: input.remediation,
|
|
66
|
+
location: {
|
|
67
|
+
kind: "line_range",
|
|
68
|
+
path: input.record.path,
|
|
69
|
+
lineStart: input.line ?? 1,
|
|
70
|
+
lineEnd: input.line ?? 1,
|
|
71
|
+
excerpt: input.message,
|
|
72
|
+
},
|
|
73
|
+
metadata: input.metadata,
|
|
74
|
+
});
|
|
75
|
+
export const runPdrFolderTreeGate = async (input) => {
|
|
76
|
+
const pdr = input.artifacts.pdr;
|
|
77
|
+
if (!pdr) {
|
|
78
|
+
return {
|
|
79
|
+
gateId: "gate-pdr-folder-tree",
|
|
80
|
+
gateName: "PDR Folder Tree",
|
|
81
|
+
status: "skipped",
|
|
82
|
+
issues: [],
|
|
83
|
+
notes: ["No PDR artifact available for folder tree validation."],
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
const issues = [];
|
|
87
|
+
const notes = [];
|
|
88
|
+
try {
|
|
89
|
+
const content = await fs.readFile(pdr.path, "utf8");
|
|
90
|
+
const lines = content.split(/\r?\n/);
|
|
91
|
+
const section = extractSection(lines, FOLDER_TREE_HEADING);
|
|
92
|
+
if (!section) {
|
|
93
|
+
issues.push(buildIssue({
|
|
94
|
+
id: "gate-pdr-folder-tree-missing-section",
|
|
95
|
+
message: "PDR is missing a target folder tree section.",
|
|
96
|
+
remediation: "Add a dedicated folder tree section with an expanded repository tree and responsibilities.",
|
|
97
|
+
record: pdr,
|
|
98
|
+
metadata: { issueType: "missing_folder_tree_section" },
|
|
99
|
+
}));
|
|
100
|
+
}
|
|
101
|
+
else {
|
|
102
|
+
const sectionText = section.content.join("\n").trim();
|
|
103
|
+
const treeBlock = extractTreeBlock(sectionText);
|
|
104
|
+
if (!treeBlock) {
|
|
105
|
+
issues.push(buildIssue({
|
|
106
|
+
id: "gate-pdr-folder-tree-missing-fence",
|
|
107
|
+
message: "Folder tree section does not include a fenced text tree block.",
|
|
108
|
+
remediation: "Add a fenced block (```text ... ```) that describes the target repository tree.",
|
|
109
|
+
record: pdr,
|
|
110
|
+
line: section.line,
|
|
111
|
+
metadata: { issueType: "missing_fenced_tree" },
|
|
112
|
+
}));
|
|
113
|
+
}
|
|
114
|
+
else {
|
|
115
|
+
const entryCount = countTreeEntries(treeBlock);
|
|
116
|
+
if (entryCount < TREE_ENTRY_MINIMUM) {
|
|
117
|
+
issues.push(buildIssue({
|
|
118
|
+
id: "gate-pdr-folder-tree-sparse-tree",
|
|
119
|
+
message: `Folder tree block is too sparse (${entryCount} entries).`,
|
|
120
|
+
remediation: "Expand the tree to include major docs/source/contracts/db/deploy/tests/scripts paths.",
|
|
121
|
+
record: pdr,
|
|
122
|
+
line: section.line,
|
|
123
|
+
metadata: { issueType: "sparse_tree", entryCount, minimum: TREE_ENTRY_MINIMUM },
|
|
124
|
+
}));
|
|
125
|
+
}
|
|
126
|
+
if (!hasResponsibilityHints(treeBlock)) {
|
|
127
|
+
issues.push(buildIssue({
|
|
128
|
+
id: "gate-pdr-folder-tree-missing-responsibilities",
|
|
129
|
+
message: "Folder tree block is missing responsibility hints for listed paths.",
|
|
130
|
+
remediation: "Annotate key paths with short purpose/ownership comments to make the structure actionable.",
|
|
131
|
+
record: pdr,
|
|
132
|
+
line: section.line,
|
|
133
|
+
metadata: { issueType: "missing_tree_responsibilities" },
|
|
134
|
+
}));
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
catch (error) {
|
|
140
|
+
notes.push(`Unable to read PDR ${pdr.path}: ${error.message ?? String(error)}`);
|
|
141
|
+
}
|
|
142
|
+
const status = issues.length === 0 ? "pass" : "fail";
|
|
143
|
+
return {
|
|
144
|
+
gateId: "gate-pdr-folder-tree",
|
|
145
|
+
gateName: "PDR Folder Tree",
|
|
146
|
+
status,
|
|
147
|
+
issues,
|
|
148
|
+
notes: notes.length ? notes : undefined,
|
|
149
|
+
metadata: { issueCount: issues.length },
|
|
150
|
+
};
|
|
151
|
+
};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { DocgenArtifactInventory } from "../../DocgenRunContext.js";
|
|
2
|
+
import { ReviewGateResult } from "../ReviewTypes.js";
|
|
3
|
+
export interface PdrNoUnresolvedItemsGateInput {
|
|
4
|
+
artifacts: DocgenArtifactInventory;
|
|
5
|
+
}
|
|
6
|
+
export declare const runPdrNoUnresolvedItemsGate: (input: PdrNoUnresolvedItemsGateInput) => Promise<ReviewGateResult>;
|
|
7
|
+
//# sourceMappingURL=PdrNoUnresolvedItemsGate.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PdrNoUnresolvedItemsGate.d.ts","sourceRoot":"","sources":["../../../../../src/services/docs/review/gates/PdrNoUnresolvedItemsGate.ts"],"names":[],"mappings":"AACA,OAAO,EAAqB,uBAAuB,EAAE,MAAM,2BAA2B,CAAC;AACvF,OAAO,EAAE,gBAAgB,EAAe,MAAM,mBAAmB,CAAC;AAElE,MAAM,WAAW,6BAA6B;IAC5C,SAAS,EAAE,uBAAuB,CAAC;CACpC;AA4CD,eAAO,MAAM,2BAA2B,GACtC,OAAO,6BAA6B,KACnC,OAAO,CAAC,gBAAgB,CAsF1B,CAAC"}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { promises as fs } from "node:fs";
|
|
2
|
+
const UNRESOLVED_PATTERNS = [
|
|
3
|
+
/\bTBD\b/i,
|
|
4
|
+
/\bTBC\b/i,
|
|
5
|
+
/\bTODO\b/i,
|
|
6
|
+
/\bFIXME\b/i,
|
|
7
|
+
/\bto be (determined|decided|filled)\b/i,
|
|
8
|
+
/\bunknown\b/i,
|
|
9
|
+
/\bunresolved\b/i,
|
|
10
|
+
];
|
|
11
|
+
const OPEN_QUESTIONS_HEADING = /open questions?/i;
|
|
12
|
+
const RESOLVED_LINE = /^[-*+\d.)\s]*resolved:/i;
|
|
13
|
+
const NO_OPEN_ITEMS_LINE = /no unresolved questions remain|no open questions remain/i;
|
|
14
|
+
const isFenceLine = (line) => /^```|^~~~/.test(line.trim());
|
|
15
|
+
const buildIssue = (input) => ({
|
|
16
|
+
id: input.id,
|
|
17
|
+
gateId: "gate-pdr-no-unresolved-items",
|
|
18
|
+
severity: "high",
|
|
19
|
+
category: "open_questions",
|
|
20
|
+
artifact: "pdr",
|
|
21
|
+
message: input.message,
|
|
22
|
+
remediation: input.remediation,
|
|
23
|
+
location: {
|
|
24
|
+
kind: "line_range",
|
|
25
|
+
path: input.record.path,
|
|
26
|
+
lineStart: input.line,
|
|
27
|
+
lineEnd: input.line,
|
|
28
|
+
excerpt: input.excerpt,
|
|
29
|
+
},
|
|
30
|
+
metadata: input.metadata,
|
|
31
|
+
});
|
|
32
|
+
export const runPdrNoUnresolvedItemsGate = async (input) => {
|
|
33
|
+
const pdr = input.artifacts.pdr;
|
|
34
|
+
if (!pdr) {
|
|
35
|
+
return {
|
|
36
|
+
gateId: "gate-pdr-no-unresolved-items",
|
|
37
|
+
gateName: "PDR No Unresolved Items",
|
|
38
|
+
status: "skipped",
|
|
39
|
+
issues: [],
|
|
40
|
+
notes: ["No PDR artifact available for unresolved item validation."],
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
const issues = [];
|
|
44
|
+
const notes = [];
|
|
45
|
+
try {
|
|
46
|
+
const content = await fs.readFile(pdr.path, "utf8");
|
|
47
|
+
const lines = content.split(/\r?\n/);
|
|
48
|
+
let inFence = false;
|
|
49
|
+
let inOpenQuestions = false;
|
|
50
|
+
for (let i = 0; i < lines.length; i += 1) {
|
|
51
|
+
const line = lines[i] ?? "";
|
|
52
|
+
const trimmed = line.trim();
|
|
53
|
+
if (!trimmed)
|
|
54
|
+
continue;
|
|
55
|
+
if (isFenceLine(trimmed)) {
|
|
56
|
+
inFence = !inFence;
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
if (inFence)
|
|
60
|
+
continue;
|
|
61
|
+
const heading = trimmed.match(/^#{1,6}\s+(.*)$/);
|
|
62
|
+
if (heading) {
|
|
63
|
+
const title = heading[1]?.trim() ?? "";
|
|
64
|
+
inOpenQuestions = OPEN_QUESTIONS_HEADING.test(title);
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
if (inOpenQuestions) {
|
|
68
|
+
if (NO_OPEN_ITEMS_LINE.test(trimmed))
|
|
69
|
+
continue;
|
|
70
|
+
if (RESOLVED_LINE.test(trimmed))
|
|
71
|
+
continue;
|
|
72
|
+
issues.push(buildIssue({
|
|
73
|
+
id: `gate-pdr-no-unresolved-items-open-${i + 1}`,
|
|
74
|
+
message: "Open Questions section contains an unresolved entry.",
|
|
75
|
+
remediation: "Rewrite entries as resolved decisions (prefix each line with 'Resolved:') or remove unresolved items.",
|
|
76
|
+
record: pdr,
|
|
77
|
+
line: i + 1,
|
|
78
|
+
excerpt: trimmed,
|
|
79
|
+
metadata: { issueType: "unresolved_open_question" },
|
|
80
|
+
}));
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
const unresolvedPattern = UNRESOLVED_PATTERNS.find((pattern) => pattern.test(trimmed));
|
|
84
|
+
if (unresolvedPattern) {
|
|
85
|
+
issues.push(buildIssue({
|
|
86
|
+
id: `gate-pdr-no-unresolved-items-marker-${i + 1}`,
|
|
87
|
+
message: "PDR contains unresolved placeholder language.",
|
|
88
|
+
remediation: "Replace unresolved markers with explicit decisions and concrete implementation details.",
|
|
89
|
+
record: pdr,
|
|
90
|
+
line: i + 1,
|
|
91
|
+
excerpt: trimmed,
|
|
92
|
+
metadata: { issueType: "unresolved_marker", pattern: unresolvedPattern.source },
|
|
93
|
+
}));
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
catch (error) {
|
|
98
|
+
notes.push(`Unable to read PDR ${pdr.path}: ${error.message ?? String(error)}`);
|
|
99
|
+
}
|
|
100
|
+
const status = issues.length === 0 ? "pass" : "fail";
|
|
101
|
+
return {
|
|
102
|
+
gateId: "gate-pdr-no-unresolved-items",
|
|
103
|
+
gateName: "PDR No Unresolved Items",
|
|
104
|
+
status,
|
|
105
|
+
issues,
|
|
106
|
+
notes: notes.length ? notes : undefined,
|
|
107
|
+
metadata: { issueCount: issues.length },
|
|
108
|
+
};
|
|
109
|
+
};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { DocgenArtifactInventory } from "../../DocgenRunContext.js";
|
|
2
|
+
import { ReviewGateResult } from "../ReviewTypes.js";
|
|
3
|
+
export interface PdrTechStackRationaleGateInput {
|
|
4
|
+
artifacts: DocgenArtifactInventory;
|
|
5
|
+
}
|
|
6
|
+
export declare const runPdrTechStackRationaleGate: (input: PdrTechStackRationaleGateInput) => Promise<ReviewGateResult>;
|
|
7
|
+
//# sourceMappingURL=PdrTechStackRationaleGate.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PdrTechStackRationaleGate.d.ts","sourceRoot":"","sources":["../../../../../src/services/docs/review/gates/PdrTechStackRationaleGate.ts"],"names":[],"mappings":"AACA,OAAO,EAAqB,uBAAuB,EAAE,MAAM,2BAA2B,CAAC;AACvF,OAAO,EAAE,gBAAgB,EAAe,MAAM,mBAAmB,CAAC;AAElE,MAAM,WAAW,8BAA8B;IAC7C,SAAS,EAAE,uBAAuB,CAAC;CACpC;AAqED,eAAO,MAAM,4BAA4B,GACvC,OAAO,8BAA8B,KACpC,OAAO,CAAC,gBAAgB,CAoF1B,CAAC"}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { promises as fs } from "node:fs";
|
|
2
|
+
const SECTION_HEADING = /platform model|technology stack|tech stack/i;
|
|
3
|
+
const CHOSEN_STACK_PATTERNS = [/chosen stack/i, /selected stack/i, /primary stack/i, /\bwe use\b/i];
|
|
4
|
+
const ALTERNATIVES_PATTERNS = [/alternatives? considered/i, /\balternative\b/i, /options? considered/i];
|
|
5
|
+
const RATIONALE_PATTERNS = [/rationale/i, /trade[- ]?off/i, /\bbecause\b/i, /why (this|the) stack/i];
|
|
6
|
+
const isFenceLine = (line) => /^```|^~~~/.test(line.trim());
|
|
7
|
+
const extractSection = (lines, headingMatch) => {
|
|
8
|
+
let inFence = false;
|
|
9
|
+
let capture = false;
|
|
10
|
+
let startLine = 0;
|
|
11
|
+
const collected = [];
|
|
12
|
+
for (let i = 0; i < lines.length; i += 1) {
|
|
13
|
+
const line = lines[i] ?? "";
|
|
14
|
+
const trimmed = line.trim();
|
|
15
|
+
if (isFenceLine(trimmed)) {
|
|
16
|
+
inFence = !inFence;
|
|
17
|
+
continue;
|
|
18
|
+
}
|
|
19
|
+
if (inFence)
|
|
20
|
+
continue;
|
|
21
|
+
const heading = trimmed.match(/^#{1,6}\s+(.*)$/);
|
|
22
|
+
if (heading) {
|
|
23
|
+
const title = heading[1]?.trim() ?? "";
|
|
24
|
+
if (headingMatch.test(title)) {
|
|
25
|
+
capture = true;
|
|
26
|
+
startLine = i + 1;
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
29
|
+
if (capture)
|
|
30
|
+
break;
|
|
31
|
+
}
|
|
32
|
+
if (capture && trimmed)
|
|
33
|
+
collected.push(trimmed);
|
|
34
|
+
}
|
|
35
|
+
if (!capture)
|
|
36
|
+
return undefined;
|
|
37
|
+
return { content: collected, line: startLine };
|
|
38
|
+
};
|
|
39
|
+
const containsAny = (lines, patterns) => lines.some((line) => patterns.some((pattern) => pattern.test(line)));
|
|
40
|
+
const buildIssue = (input) => ({
|
|
41
|
+
id: input.id,
|
|
42
|
+
gateId: "gate-pdr-tech-stack-rationale",
|
|
43
|
+
severity: "high",
|
|
44
|
+
category: "decision",
|
|
45
|
+
artifact: "pdr",
|
|
46
|
+
message: input.message,
|
|
47
|
+
remediation: input.remediation,
|
|
48
|
+
location: {
|
|
49
|
+
kind: "line_range",
|
|
50
|
+
path: input.record.path,
|
|
51
|
+
lineStart: input.line ?? 1,
|
|
52
|
+
lineEnd: input.line ?? 1,
|
|
53
|
+
excerpt: input.message,
|
|
54
|
+
},
|
|
55
|
+
metadata: input.metadata,
|
|
56
|
+
});
|
|
57
|
+
export const runPdrTechStackRationaleGate = async (input) => {
|
|
58
|
+
const pdr = input.artifacts.pdr;
|
|
59
|
+
if (!pdr) {
|
|
60
|
+
return {
|
|
61
|
+
gateId: "gate-pdr-tech-stack-rationale",
|
|
62
|
+
gateName: "PDR Tech Stack Rationale",
|
|
63
|
+
status: "skipped",
|
|
64
|
+
issues: [],
|
|
65
|
+
notes: ["No PDR artifact available for tech stack rationale validation."],
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
const issues = [];
|
|
69
|
+
const notes = [];
|
|
70
|
+
try {
|
|
71
|
+
const content = await fs.readFile(pdr.path, "utf8");
|
|
72
|
+
const lines = content.split(/\r?\n/);
|
|
73
|
+
const stackSection = extractSection(lines, SECTION_HEADING);
|
|
74
|
+
if (!stackSection) {
|
|
75
|
+
issues.push(buildIssue({
|
|
76
|
+
id: "gate-pdr-tech-stack-rationale-missing-section",
|
|
77
|
+
message: "PDR is missing a platform model / technology stack section.",
|
|
78
|
+
remediation: "Add a section that defines the selected stack, alternatives considered, and decision rationale.",
|
|
79
|
+
record: pdr,
|
|
80
|
+
metadata: { issueType: "missing_tech_stack_section" },
|
|
81
|
+
}));
|
|
82
|
+
}
|
|
83
|
+
else {
|
|
84
|
+
if (!containsAny(stackSection.content, CHOSEN_STACK_PATTERNS)) {
|
|
85
|
+
issues.push(buildIssue({
|
|
86
|
+
id: "gate-pdr-tech-stack-rationale-missing-chosen",
|
|
87
|
+
message: "Tech stack section does not explicitly state the chosen stack baseline.",
|
|
88
|
+
remediation: "Add explicit chosen/selected stack statements for runtime, language, persistence, and tooling.",
|
|
89
|
+
record: pdr,
|
|
90
|
+
line: stackSection.line,
|
|
91
|
+
metadata: { issueType: "missing_chosen_stack" },
|
|
92
|
+
}));
|
|
93
|
+
}
|
|
94
|
+
if (!containsAny(stackSection.content, ALTERNATIVES_PATTERNS)) {
|
|
95
|
+
issues.push(buildIssue({
|
|
96
|
+
id: "gate-pdr-tech-stack-rationale-missing-alternatives",
|
|
97
|
+
message: "Tech stack section does not document alternatives considered.",
|
|
98
|
+
remediation: "Document at least one realistic alternative and explain why it was not selected.",
|
|
99
|
+
record: pdr,
|
|
100
|
+
line: stackSection.line,
|
|
101
|
+
metadata: { issueType: "missing_alternatives" },
|
|
102
|
+
}));
|
|
103
|
+
}
|
|
104
|
+
if (!containsAny(stackSection.content, RATIONALE_PATTERNS)) {
|
|
105
|
+
issues.push(buildIssue({
|
|
106
|
+
id: "gate-pdr-tech-stack-rationale-missing-rationale",
|
|
107
|
+
message: "Tech stack section does not include explicit decision rationale/trade-offs.",
|
|
108
|
+
remediation: "Add rationale that explains trade-offs and why the selected stack is preferred for this phase.",
|
|
109
|
+
record: pdr,
|
|
110
|
+
line: stackSection.line,
|
|
111
|
+
metadata: { issueType: "missing_rationale" },
|
|
112
|
+
}));
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
catch (error) {
|
|
117
|
+
notes.push(`Unable to read PDR ${pdr.path}: ${error.message ?? String(error)}`);
|
|
118
|
+
}
|
|
119
|
+
const status = issues.length === 0 ? "pass" : "fail";
|
|
120
|
+
return {
|
|
121
|
+
gateId: "gate-pdr-tech-stack-rationale",
|
|
122
|
+
gateName: "PDR Tech Stack Rationale",
|
|
123
|
+
status,
|
|
124
|
+
issues,
|
|
125
|
+
notes: notes.length ? notes : undefined,
|
|
126
|
+
metadata: { issueCount: issues.length },
|
|
127
|
+
};
|
|
128
|
+
};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { DocgenArtifactInventory } from "../../DocgenRunContext.js";
|
|
2
|
+
import { ReviewGateResult } from "../ReviewTypes.js";
|
|
3
|
+
export interface SdsFolderTreeGateInput {
|
|
4
|
+
artifacts: DocgenArtifactInventory;
|
|
5
|
+
}
|
|
6
|
+
export declare const runSdsFolderTreeGate: (input: SdsFolderTreeGateInput) => Promise<ReviewGateResult>;
|
|
7
|
+
//# sourceMappingURL=SdsFolderTreeGate.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SdsFolderTreeGate.d.ts","sourceRoot":"","sources":["../../../../../src/services/docs/review/gates/SdsFolderTreeGate.ts"],"names":[],"mappings":"AACA,OAAO,EAAqB,uBAAuB,EAAE,MAAM,2BAA2B,CAAC;AACvF,OAAO,EAAE,gBAAgB,EAAe,MAAM,mBAAmB,CAAC;AAElE,MAAM,WAAW,sBAAsB;IACrC,SAAS,EAAE,uBAAuB,CAAC;CACpC;AAsFD,eAAO,MAAM,oBAAoB,GAC/B,OAAO,sBAAsB,KAC5B,OAAO,CAAC,gBAAgB,CAwF1B,CAAC"}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { promises as fs } from "node:fs";
|
|
2
|
+
const FOLDER_TREE_HEADING = /folder tree|directory structure|repository structure|target structure/i;
|
|
3
|
+
const TREE_ENTRY_MINIMUM = 8;
|
|
4
|
+
const isFenceLine = (line) => /^```|^~~~/.test(line.trim());
|
|
5
|
+
const extractSection = (lines, headingMatch) => {
|
|
6
|
+
let inFence = false;
|
|
7
|
+
let capture = false;
|
|
8
|
+
let startLine = 0;
|
|
9
|
+
const collected = [];
|
|
10
|
+
for (let i = 0; i < lines.length; i += 1) {
|
|
11
|
+
const line = lines[i] ?? "";
|
|
12
|
+
const trimmed = line.trim();
|
|
13
|
+
if (isFenceLine(trimmed)) {
|
|
14
|
+
inFence = !inFence;
|
|
15
|
+
if (capture)
|
|
16
|
+
collected.push(trimmed);
|
|
17
|
+
continue;
|
|
18
|
+
}
|
|
19
|
+
const heading = trimmed.match(/^#{1,6}\s+(.*)$/);
|
|
20
|
+
if (heading && !inFence) {
|
|
21
|
+
const title = heading[1]?.trim() ?? "";
|
|
22
|
+
if (headingMatch.test(title)) {
|
|
23
|
+
capture = true;
|
|
24
|
+
startLine = i + 1;
|
|
25
|
+
continue;
|
|
26
|
+
}
|
|
27
|
+
if (capture)
|
|
28
|
+
break;
|
|
29
|
+
}
|
|
30
|
+
if (capture)
|
|
31
|
+
collected.push(line);
|
|
32
|
+
}
|
|
33
|
+
if (!capture)
|
|
34
|
+
return undefined;
|
|
35
|
+
return { content: collected, line: startLine };
|
|
36
|
+
};
|
|
37
|
+
const extractTreeBlock = (content) => {
|
|
38
|
+
const match = content.match(/```(?:text)?\s*([\s\S]*?)```/i);
|
|
39
|
+
if (!match)
|
|
40
|
+
return undefined;
|
|
41
|
+
return match[1]?.trim();
|
|
42
|
+
};
|
|
43
|
+
const countTreeEntries = (treeBlock) => {
|
|
44
|
+
return treeBlock
|
|
45
|
+
.split(/\r?\n/)
|
|
46
|
+
.map((line) => line.trim())
|
|
47
|
+
.filter((line) => {
|
|
48
|
+
if (!line)
|
|
49
|
+
return false;
|
|
50
|
+
if (line === ".")
|
|
51
|
+
return true;
|
|
52
|
+
if (/^[├└│]/.test(line))
|
|
53
|
+
return true;
|
|
54
|
+
if (/[A-Za-z0-9_.-]+\/?(\s+[#-].*)?$/.test(line))
|
|
55
|
+
return true;
|
|
56
|
+
return false;
|
|
57
|
+
}).length;
|
|
58
|
+
};
|
|
59
|
+
const hasResponsibilityHints = (treeBlock) => /#|responsibilit|owner|module|service|tests?|scripts?/i.test(treeBlock);
|
|
60
|
+
const buildIssue = (input) => ({
|
|
61
|
+
id: input.id,
|
|
62
|
+
gateId: "gate-sds-folder-tree",
|
|
63
|
+
severity: "high",
|
|
64
|
+
category: "completeness",
|
|
65
|
+
artifact: "sds",
|
|
66
|
+
message: input.message,
|
|
67
|
+
remediation: input.remediation,
|
|
68
|
+
location: {
|
|
69
|
+
kind: "line_range",
|
|
70
|
+
path: input.record.path,
|
|
71
|
+
lineStart: input.line ?? 1,
|
|
72
|
+
lineEnd: input.line ?? 1,
|
|
73
|
+
excerpt: input.message,
|
|
74
|
+
},
|
|
75
|
+
metadata: input.metadata,
|
|
76
|
+
});
|
|
77
|
+
export const runSdsFolderTreeGate = async (input) => {
|
|
78
|
+
const sds = input.artifacts.sds;
|
|
79
|
+
if (!sds) {
|
|
80
|
+
return {
|
|
81
|
+
gateId: "gate-sds-folder-tree",
|
|
82
|
+
gateName: "SDS Folder Tree",
|
|
83
|
+
status: "skipped",
|
|
84
|
+
issues: [],
|
|
85
|
+
notes: ["No SDS artifact available for folder tree validation."],
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
const issues = [];
|
|
89
|
+
const notes = [];
|
|
90
|
+
try {
|
|
91
|
+
const content = await fs.readFile(sds.path, "utf8");
|
|
92
|
+
const lines = content.split(/\r?\n/);
|
|
93
|
+
const section = extractSection(lines, FOLDER_TREE_HEADING);
|
|
94
|
+
if (!section) {
|
|
95
|
+
issues.push(buildIssue({
|
|
96
|
+
id: "gate-sds-folder-tree-missing-section",
|
|
97
|
+
message: "SDS is missing a target folder tree section.",
|
|
98
|
+
remediation: "Add a dedicated folder tree section with an expanded repository tree and responsibilities.",
|
|
99
|
+
record: sds,
|
|
100
|
+
metadata: { issueType: "missing_folder_tree_section" },
|
|
101
|
+
}));
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
const sectionText = section.content.join("\n").trim();
|
|
105
|
+
const treeBlock = extractTreeBlock(sectionText);
|
|
106
|
+
if (!treeBlock) {
|
|
107
|
+
issues.push(buildIssue({
|
|
108
|
+
id: "gate-sds-folder-tree-missing-fence",
|
|
109
|
+
message: "Folder tree section does not include a fenced text tree block.",
|
|
110
|
+
remediation: "Add a fenced block (```text ... ```) that describes the target repository tree.",
|
|
111
|
+
record: sds,
|
|
112
|
+
line: section.line,
|
|
113
|
+
metadata: { issueType: "missing_fenced_tree" },
|
|
114
|
+
}));
|
|
115
|
+
}
|
|
116
|
+
else {
|
|
117
|
+
const entryCount = countTreeEntries(treeBlock);
|
|
118
|
+
if (entryCount < TREE_ENTRY_MINIMUM) {
|
|
119
|
+
issues.push(buildIssue({
|
|
120
|
+
id: "gate-sds-folder-tree-sparse-tree",
|
|
121
|
+
message: `Folder tree block is too sparse (${entryCount} entries).`,
|
|
122
|
+
remediation: "Expand the tree to include major docs/source/contracts/db/deploy/tests/scripts paths.",
|
|
123
|
+
record: sds,
|
|
124
|
+
line: section.line,
|
|
125
|
+
metadata: { issueType: "sparse_tree", entryCount, minimum: TREE_ENTRY_MINIMUM },
|
|
126
|
+
}));
|
|
127
|
+
}
|
|
128
|
+
if (!hasResponsibilityHints(treeBlock)) {
|
|
129
|
+
issues.push(buildIssue({
|
|
130
|
+
id: "gate-sds-folder-tree-missing-responsibilities",
|
|
131
|
+
message: "Folder tree block is missing responsibility hints for listed paths.",
|
|
132
|
+
remediation: "Annotate key paths with short purpose/ownership comments to make the structure actionable.",
|
|
133
|
+
record: sds,
|
|
134
|
+
line: section.line,
|
|
135
|
+
metadata: { issueType: "missing_tree_responsibilities" },
|
|
136
|
+
}));
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
catch (error) {
|
|
142
|
+
notes.push(`Unable to read SDS ${sds.path}: ${error.message ?? String(error)}`);
|
|
143
|
+
}
|
|
144
|
+
const status = issues.length === 0 ? "pass" : "fail";
|
|
145
|
+
return {
|
|
146
|
+
gateId: "gate-sds-folder-tree",
|
|
147
|
+
gateName: "SDS Folder Tree",
|
|
148
|
+
status,
|
|
149
|
+
issues,
|
|
150
|
+
notes: notes.length ? notes : undefined,
|
|
151
|
+
metadata: { issueCount: issues.length },
|
|
152
|
+
};
|
|
153
|
+
};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { DocgenArtifactInventory } from "../../DocgenRunContext.js";
|
|
2
|
+
import { ReviewGateResult } from "../ReviewTypes.js";
|
|
3
|
+
export interface SdsNoUnresolvedItemsGateInput {
|
|
4
|
+
artifacts: DocgenArtifactInventory;
|
|
5
|
+
}
|
|
6
|
+
export declare const runSdsNoUnresolvedItemsGate: (input: SdsNoUnresolvedItemsGateInput) => Promise<ReviewGateResult>;
|
|
7
|
+
//# sourceMappingURL=SdsNoUnresolvedItemsGate.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SdsNoUnresolvedItemsGate.d.ts","sourceRoot":"","sources":["../../../../../src/services/docs/review/gates/SdsNoUnresolvedItemsGate.ts"],"names":[],"mappings":"AACA,OAAO,EAAqB,uBAAuB,EAAE,MAAM,2BAA2B,CAAC;AACvF,OAAO,EAAE,gBAAgB,EAAe,MAAM,mBAAmB,CAAC;AAElE,MAAM,WAAW,6BAA6B;IAC5C,SAAS,EAAE,uBAAuB,CAAC;CACpC;AA4CD,eAAO,MAAM,2BAA2B,GACtC,OAAO,6BAA6B,KACnC,OAAO,CAAC,gBAAgB,CAsF1B,CAAC"}
|