@harness-engineering/core 0.10.1 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/architecture/matchers.d.mts +2 -0
- package/dist/architecture/matchers.d.ts +2 -0
- package/dist/architecture/matchers.js +1784 -0
- package/dist/architecture/matchers.mjs +10 -0
- package/dist/chunk-ZHGBWFYD.mjs +1827 -0
- package/dist/index.d.mts +892 -23
- package/dist/index.d.ts +892 -23
- package/dist/index.js +2241 -320
- package/dist/index.mjs +1161 -1026
- package/dist/matchers-D20x48U9.d.mts +353 -0
- package/dist/matchers-D20x48U9.d.ts +353 -0
- package/package.json +11 -3
package/dist/index.js
CHANGED
|
@@ -34,33 +34,58 @@ __export(index_exports, {
|
|
|
34
34
|
AGENT_DESCRIPTORS: () => AGENT_DESCRIPTORS,
|
|
35
35
|
ARCHITECTURE_DESCRIPTOR: () => ARCHITECTURE_DESCRIPTOR,
|
|
36
36
|
AgentActionEmitter: () => AgentActionEmitter,
|
|
37
|
+
ArchBaselineManager: () => ArchBaselineManager,
|
|
38
|
+
ArchBaselineSchema: () => ArchBaselineSchema,
|
|
39
|
+
ArchConfigSchema: () => ArchConfigSchema,
|
|
40
|
+
ArchDiffResultSchema: () => ArchDiffResultSchema,
|
|
41
|
+
ArchMetricCategorySchema: () => ArchMetricCategorySchema,
|
|
37
42
|
BUG_DETECTION_DESCRIPTOR: () => BUG_DETECTION_DESCRIPTOR,
|
|
38
43
|
BaselineManager: () => BaselineManager,
|
|
39
44
|
BenchmarkRunner: () => BenchmarkRunner,
|
|
45
|
+
BlueprintGenerator: () => BlueprintGenerator,
|
|
46
|
+
BundleConstraintsSchema: () => BundleConstraintsSchema,
|
|
47
|
+
BundleSchema: () => BundleSchema,
|
|
40
48
|
COMPLIANCE_DESCRIPTOR: () => COMPLIANCE_DESCRIPTOR,
|
|
49
|
+
CategoryBaselineSchema: () => CategoryBaselineSchema,
|
|
50
|
+
CategoryRegressionSchema: () => CategoryRegressionSchema,
|
|
41
51
|
ChecklistBuilder: () => ChecklistBuilder,
|
|
52
|
+
CircularDepsCollector: () => CircularDepsCollector,
|
|
53
|
+
ComplexityCollector: () => ComplexityCollector,
|
|
42
54
|
ConfirmationSchema: () => ConfirmationSchema,
|
|
43
55
|
ConsoleSink: () => ConsoleSink,
|
|
56
|
+
ConstraintRuleSchema: () => ConstraintRuleSchema,
|
|
57
|
+
ContentPipeline: () => ContentPipeline,
|
|
58
|
+
ContributionsSchema: () => ContributionsSchema,
|
|
59
|
+
CouplingCollector: () => CouplingCollector,
|
|
44
60
|
CriticalPathResolver: () => CriticalPathResolver,
|
|
45
61
|
DEFAULT_PROVIDER_TIERS: () => DEFAULT_PROVIDER_TIERS,
|
|
46
62
|
DEFAULT_SECURITY_CONFIG: () => DEFAULT_SECURITY_CONFIG,
|
|
47
63
|
DEFAULT_STATE: () => DEFAULT_STATE,
|
|
48
64
|
DEFAULT_STREAM_INDEX: () => DEFAULT_STREAM_INDEX,
|
|
65
|
+
DepDepthCollector: () => DepDepthCollector,
|
|
49
66
|
EmitInteractionInputSchema: () => EmitInteractionInputSchema,
|
|
50
67
|
EntropyAnalyzer: () => EntropyAnalyzer,
|
|
51
68
|
EntropyConfigSchema: () => EntropyConfigSchema,
|
|
52
69
|
ExclusionSet: () => ExclusionSet,
|
|
53
70
|
FailureEntrySchema: () => FailureEntrySchema,
|
|
54
71
|
FileSink: () => FileSink,
|
|
72
|
+
ForbiddenImportCollector: () => ForbiddenImportCollector,
|
|
55
73
|
GateConfigSchema: () => GateConfigSchema,
|
|
56
74
|
GateResultSchema: () => GateResultSchema,
|
|
57
75
|
HandoffSchema: () => HandoffSchema,
|
|
58
76
|
HarnessStateSchema: () => HarnessStateSchema,
|
|
59
77
|
InteractionTypeSchema: () => InteractionTypeSchema,
|
|
78
|
+
LayerViolationCollector: () => LayerViolationCollector,
|
|
79
|
+
LockfilePackageSchema: () => LockfilePackageSchema,
|
|
80
|
+
LockfileSchema: () => LockfileSchema,
|
|
81
|
+
ManifestSchema: () => ManifestSchema,
|
|
82
|
+
MetricResultSchema: () => MetricResultSchema,
|
|
83
|
+
ModuleSizeCollector: () => ModuleSizeCollector,
|
|
60
84
|
NoOpExecutor: () => NoOpExecutor,
|
|
61
85
|
NoOpSink: () => NoOpSink,
|
|
62
86
|
NoOpTelemetryAdapter: () => NoOpTelemetryAdapter,
|
|
63
87
|
PatternConfigSchema: () => PatternConfigSchema,
|
|
88
|
+
ProjectScanner: () => ProjectScanner,
|
|
64
89
|
QuestionSchema: () => QuestionSchema,
|
|
65
90
|
REQUIRED_SECTIONS: () => REQUIRED_SECTIONS,
|
|
66
91
|
RegressionDetector: () => RegressionDetector,
|
|
@@ -68,16 +93,26 @@ __export(index_exports, {
|
|
|
68
93
|
SECURITY_DESCRIPTOR: () => SECURITY_DESCRIPTOR,
|
|
69
94
|
SecurityConfigSchema: () => SecurityConfigSchema,
|
|
70
95
|
SecurityScanner: () => SecurityScanner,
|
|
96
|
+
SharableBoundaryConfigSchema: () => SharableBoundaryConfigSchema,
|
|
97
|
+
SharableForbiddenImportSchema: () => SharableForbiddenImportSchema,
|
|
98
|
+
SharableLayerSchema: () => SharableLayerSchema,
|
|
99
|
+
SharableSecurityRulesSchema: () => SharableSecurityRulesSchema,
|
|
71
100
|
StreamIndexSchema: () => StreamIndexSchema,
|
|
72
101
|
StreamInfoSchema: () => StreamInfoSchema,
|
|
102
|
+
ThresholdConfigSchema: () => ThresholdConfigSchema,
|
|
73
103
|
TransitionSchema: () => TransitionSchema,
|
|
74
104
|
TypeScriptParser: () => TypeScriptParser,
|
|
75
105
|
VERSION: () => VERSION,
|
|
106
|
+
ViolationSchema: () => ViolationSchema,
|
|
107
|
+
addProvenance: () => addProvenance,
|
|
76
108
|
analyzeDiff: () => analyzeDiff,
|
|
77
109
|
appendFailure: () => appendFailure,
|
|
78
110
|
appendLearning: () => appendLearning,
|
|
79
111
|
applyFixes: () => applyFixes,
|
|
80
112
|
applyHotspotDowngrade: () => applyHotspotDowngrade,
|
|
113
|
+
archMatchers: () => archMatchers,
|
|
114
|
+
archModule: () => archModule,
|
|
115
|
+
architecture: () => architecture,
|
|
81
116
|
archiveFailures: () => archiveFailures,
|
|
82
117
|
archiveStream: () => archiveStream,
|
|
83
118
|
buildDependencyGraph: () => buildDependencyGraph,
|
|
@@ -87,6 +122,7 @@ __export(index_exports, {
|
|
|
87
122
|
checkEligibility: () => checkEligibility,
|
|
88
123
|
classifyFinding: () => classifyFinding,
|
|
89
124
|
configureFeedback: () => configureFeedback,
|
|
125
|
+
constraintRuleId: () => constraintRuleId,
|
|
90
126
|
contextBudget: () => contextBudget,
|
|
91
127
|
contextFilter: () => contextFilter,
|
|
92
128
|
createBoundaryValidator: () => createBoundaryValidator,
|
|
@@ -101,6 +137,8 @@ __export(index_exports, {
|
|
|
101
137
|
cryptoRules: () => cryptoRules,
|
|
102
138
|
deduplicateCleanupFindings: () => deduplicateCleanupFindings,
|
|
103
139
|
deduplicateFindings: () => deduplicateFindings,
|
|
140
|
+
deepMergeConstraints: () => deepMergeConstraints,
|
|
141
|
+
defaultCollectors: () => defaultCollectors,
|
|
104
142
|
defineLayer: () => defineLayer,
|
|
105
143
|
deserializationRules: () => deserializationRules,
|
|
106
144
|
detectChangeType: () => detectChangeType,
|
|
@@ -113,9 +151,12 @@ __export(index_exports, {
|
|
|
113
151
|
detectPatternViolations: () => detectPatternViolations,
|
|
114
152
|
detectSizeBudgetViolations: () => detectSizeBudgetViolations,
|
|
115
153
|
detectStack: () => detectStack,
|
|
154
|
+
detectStaleConstraints: () => detectStaleConstraints,
|
|
116
155
|
determineAssessment: () => determineAssessment,
|
|
156
|
+
diff: () => diff,
|
|
117
157
|
executeWorkflow: () => executeWorkflow,
|
|
118
158
|
expressRules: () => expressRules,
|
|
159
|
+
extractBundle: () => extractBundle,
|
|
119
160
|
extractMarkdownLinks: () => extractMarkdownLinks,
|
|
120
161
|
extractSections: () => extractSections,
|
|
121
162
|
fanOutReview: () => fanOutReview,
|
|
@@ -146,6 +187,7 @@ __export(index_exports, {
|
|
|
146
187
|
networkRules: () => networkRules,
|
|
147
188
|
nodeRules: () => nodeRules,
|
|
148
189
|
parseDiff: () => parseDiff,
|
|
190
|
+
parseManifest: () => parseManifest,
|
|
149
191
|
parseRoadmap: () => parseRoadmap,
|
|
150
192
|
parseSecurityConfig: () => parseSecurityConfig,
|
|
151
193
|
parseSize: () => parseSize,
|
|
@@ -153,6 +195,8 @@ __export(index_exports, {
|
|
|
153
195
|
previewFix: () => previewFix,
|
|
154
196
|
reactRules: () => reactRules,
|
|
155
197
|
readCheckState: () => readCheckState,
|
|
198
|
+
readLockfile: () => readLockfile,
|
|
199
|
+
removeProvenance: () => removeProvenance,
|
|
156
200
|
requestMultiplePeerReviews: () => requestMultiplePeerReviews,
|
|
157
201
|
requestPeerReview: () => requestPeerReview,
|
|
158
202
|
resetFeedbackConfig: () => resetFeedbackConfig,
|
|
@@ -160,6 +204,8 @@ __export(index_exports, {
|
|
|
160
204
|
resolveModelTier: () => resolveModelTier,
|
|
161
205
|
resolveRuleSeverity: () => resolveRuleSeverity,
|
|
162
206
|
resolveStreamPath: () => resolveStreamPath,
|
|
207
|
+
resolveThresholds: () => resolveThresholds,
|
|
208
|
+
runAll: () => runAll,
|
|
163
209
|
runArchitectureAgent: () => runArchitectureAgent,
|
|
164
210
|
runBugDetectionAgent: () => runBugDetectionAgent,
|
|
165
211
|
runCIChecks: () => runCIChecks,
|
|
@@ -179,6 +225,7 @@ __export(index_exports, {
|
|
|
179
225
|
setActiveStream: () => setActiveStream,
|
|
180
226
|
shouldRunCheck: () => shouldRunCheck,
|
|
181
227
|
spawnBackgroundCheck: () => spawnBackgroundCheck,
|
|
228
|
+
syncConstraintNodes: () => syncConstraintNodes,
|
|
182
229
|
syncRoadmap: () => syncRoadmap,
|
|
183
230
|
touchStream: () => touchStream,
|
|
184
231
|
trackAction: () => trackAction,
|
|
@@ -191,6 +238,9 @@ __export(index_exports, {
|
|
|
191
238
|
validateFindings: () => validateFindings,
|
|
192
239
|
validateKnowledgeMap: () => validateKnowledgeMap,
|
|
193
240
|
validatePatternConfig: () => validatePatternConfig,
|
|
241
|
+
violationId: () => violationId,
|
|
242
|
+
writeConfig: () => writeConfig,
|
|
243
|
+
writeLockfile: () => writeLockfile,
|
|
194
244
|
xssRules: () => xssRules
|
|
195
245
|
});
|
|
196
246
|
module.exports = __toCommonJS(index_exports);
|
|
@@ -213,17 +263,17 @@ var import_util = require("util");
|
|
|
213
263
|
var import_glob = require("glob");
|
|
214
264
|
var accessAsync = (0, import_util.promisify)(import_fs.access);
|
|
215
265
|
var readFileAsync = (0, import_util.promisify)(import_fs.readFile);
|
|
216
|
-
async function fileExists(
|
|
266
|
+
async function fileExists(path13) {
|
|
217
267
|
try {
|
|
218
|
-
await accessAsync(
|
|
268
|
+
await accessAsync(path13, import_fs.constants.F_OK);
|
|
219
269
|
return true;
|
|
220
270
|
} catch {
|
|
221
271
|
return false;
|
|
222
272
|
}
|
|
223
273
|
}
|
|
224
|
-
async function readFileContent(
|
|
274
|
+
async function readFileContent(path13) {
|
|
225
275
|
try {
|
|
226
|
-
const content = await readFileAsync(
|
|
276
|
+
const content = await readFileAsync(path13, "utf-8");
|
|
227
277
|
return (0, import_types.Ok)(content);
|
|
228
278
|
} catch (error) {
|
|
229
279
|
return (0, import_types.Err)(error);
|
|
@@ -271,15 +321,15 @@ function validateConfig(data, schema) {
|
|
|
271
321
|
let message = "Configuration validation failed";
|
|
272
322
|
const suggestions = [];
|
|
273
323
|
if (firstError) {
|
|
274
|
-
const
|
|
275
|
-
const pathDisplay =
|
|
324
|
+
const path13 = firstError.path.join(".");
|
|
325
|
+
const pathDisplay = path13 ? ` at "${path13}"` : "";
|
|
276
326
|
if (firstError.code === "invalid_type") {
|
|
277
327
|
const received = firstError.received;
|
|
278
328
|
const expected = firstError.expected;
|
|
279
329
|
if (received === "undefined") {
|
|
280
330
|
code = "MISSING_FIELD";
|
|
281
331
|
message = `Missing required field${pathDisplay}: ${firstError.message}`;
|
|
282
|
-
suggestions.push(`Field "${
|
|
332
|
+
suggestions.push(`Field "${path13}" is required and must be of type "${expected}"`);
|
|
283
333
|
} else {
|
|
284
334
|
code = "INVALID_TYPE";
|
|
285
335
|
message = `Invalid type${pathDisplay}: ${firstError.message}`;
|
|
@@ -492,30 +542,30 @@ function extractSections(content) {
|
|
|
492
542
|
return result;
|
|
493
543
|
});
|
|
494
544
|
}
|
|
495
|
-
function isExternalLink(
|
|
496
|
-
return
|
|
545
|
+
function isExternalLink(path13) {
|
|
546
|
+
return path13.startsWith("http://") || path13.startsWith("https://") || path13.startsWith("#") || path13.startsWith("mailto:");
|
|
497
547
|
}
|
|
498
548
|
function resolveLinkPath(linkPath, baseDir) {
|
|
499
549
|
return linkPath.startsWith(".") ? (0, import_path.join)(baseDir, linkPath) : linkPath;
|
|
500
550
|
}
|
|
501
|
-
async function validateAgentsMap(
|
|
551
|
+
async function validateAgentsMap(path13 = "./AGENTS.md") {
|
|
502
552
|
console.warn(
|
|
503
553
|
"[harness] validateAgentsMap() is deprecated. Use graph-based validation via Assembler.checkCoverage() from @harness-engineering/graph"
|
|
504
554
|
);
|
|
505
|
-
const contentResult = await readFileContent(
|
|
555
|
+
const contentResult = await readFileContent(path13);
|
|
506
556
|
if (!contentResult.ok) {
|
|
507
557
|
return (0, import_types.Err)(
|
|
508
558
|
createError(
|
|
509
559
|
"PARSE_ERROR",
|
|
510
560
|
`Failed to read AGENTS.md: ${contentResult.error.message}`,
|
|
511
|
-
{ path:
|
|
561
|
+
{ path: path13 },
|
|
512
562
|
["Ensure the file exists", "Check file permissions"]
|
|
513
563
|
)
|
|
514
564
|
);
|
|
515
565
|
}
|
|
516
566
|
const content = contentResult.value;
|
|
517
567
|
const sections = extractSections(content);
|
|
518
|
-
const baseDir = (0, import_path.dirname)(
|
|
568
|
+
const baseDir = (0, import_path.dirname)(path13);
|
|
519
569
|
const sectionTitles = sections.map((s) => s.title);
|
|
520
570
|
const missingSections = REQUIRED_SECTIONS.filter(
|
|
521
571
|
(required) => !sectionTitles.some((title) => title.toLowerCase().includes(required.toLowerCase()))
|
|
@@ -555,6 +605,7 @@ async function validateAgentsMap(path11 = "./AGENTS.md") {
|
|
|
555
605
|
}
|
|
556
606
|
|
|
557
607
|
// src/context/doc-coverage.ts
|
|
608
|
+
var import_minimatch = require("minimatch");
|
|
558
609
|
var import_path2 = require("path");
|
|
559
610
|
function determineImportance(filePath) {
|
|
560
611
|
const name = (0, import_path2.basename)(filePath).toLowerCase();
|
|
@@ -577,7 +628,7 @@ function suggestSection(filePath, domain) {
|
|
|
577
628
|
return `${domain} Reference`;
|
|
578
629
|
}
|
|
579
630
|
async function checkDocCoverage(domain, options = {}) {
|
|
580
|
-
const { docsDir = "./docs", sourceDir = "
|
|
631
|
+
const { docsDir = "./docs", sourceDir = ".", excludePatterns = [], graphCoverage } = options;
|
|
581
632
|
if (graphCoverage) {
|
|
582
633
|
const gaps = graphCoverage.undocumented.map((file) => ({
|
|
583
634
|
file,
|
|
@@ -597,9 +648,7 @@ async function checkDocCoverage(domain, options = {}) {
|
|
|
597
648
|
const filteredSourceFiles = sourceFiles.filter((file) => {
|
|
598
649
|
const relativePath = (0, import_path2.relative)(sourceDir, file);
|
|
599
650
|
return !excludePatterns.some((pattern) => {
|
|
600
|
-
|
|
601
|
-
const regex = new RegExp("^" + escaped + "$");
|
|
602
|
-
return regex.test(relativePath) || regex.test(file);
|
|
651
|
+
return (0, import_minimatch.minimatch)(relativePath, pattern, { dot: true }) || (0, import_minimatch.minimatch)(file, pattern, { dot: true });
|
|
603
652
|
});
|
|
604
653
|
});
|
|
605
654
|
const docFiles = await findFiles("**/*.md", docsDir);
|
|
@@ -657,8 +706,8 @@ async function checkDocCoverage(domain, options = {}) {
|
|
|
657
706
|
|
|
658
707
|
// src/context/knowledge-map.ts
|
|
659
708
|
var import_path3 = require("path");
|
|
660
|
-
function suggestFix(
|
|
661
|
-
const targetName = (0, import_path3.basename)(
|
|
709
|
+
function suggestFix(path13, existingFiles) {
|
|
710
|
+
const targetName = (0, import_path3.basename)(path13).toLowerCase();
|
|
662
711
|
const similar = existingFiles.find((file) => {
|
|
663
712
|
const fileName = (0, import_path3.basename)(file).toLowerCase();
|
|
664
713
|
return fileName.includes(targetName) || targetName.includes(fileName);
|
|
@@ -666,7 +715,7 @@ function suggestFix(path11, existingFiles) {
|
|
|
666
715
|
if (similar) {
|
|
667
716
|
return `Did you mean "${similar}"?`;
|
|
668
717
|
}
|
|
669
|
-
return `Create the file "${
|
|
718
|
+
return `Create the file "${path13}" or remove the link`;
|
|
670
719
|
}
|
|
671
720
|
async function validateKnowledgeMap(rootDir = process.cwd()) {
|
|
672
721
|
console.warn(
|
|
@@ -1011,7 +1060,7 @@ function getPhaseCategories(phase) {
|
|
|
1011
1060
|
}
|
|
1012
1061
|
|
|
1013
1062
|
// src/constraints/layers.ts
|
|
1014
|
-
var
|
|
1063
|
+
var import_minimatch2 = require("minimatch");
|
|
1015
1064
|
function defineLayer(name, patterns, allowedDependencies) {
|
|
1016
1065
|
return {
|
|
1017
1066
|
name,
|
|
@@ -1022,7 +1071,7 @@ function defineLayer(name, patterns, allowedDependencies) {
|
|
|
1022
1071
|
function resolveFileToLayer(file, layers) {
|
|
1023
1072
|
for (const layer of layers) {
|
|
1024
1073
|
for (const pattern of layer.patterns) {
|
|
1025
|
-
if ((0,
|
|
1074
|
+
if ((0, import_minimatch2.minimatch)(file, pattern)) {
|
|
1026
1075
|
return layer;
|
|
1027
1076
|
}
|
|
1028
1077
|
}
|
|
@@ -1269,8 +1318,8 @@ function createBoundaryValidator(schema, name) {
|
|
|
1269
1318
|
return (0, import_types.Ok)(result.data);
|
|
1270
1319
|
}
|
|
1271
1320
|
const suggestions = result.error.issues.map((issue) => {
|
|
1272
|
-
const
|
|
1273
|
-
return
|
|
1321
|
+
const path13 = issue.path.join(".");
|
|
1322
|
+
return path13 ? `${path13}: ${issue.message}` : issue.message;
|
|
1274
1323
|
});
|
|
1275
1324
|
return (0, import_types.Err)(
|
|
1276
1325
|
createError(
|
|
@@ -1314,6 +1363,422 @@ function validateBoundaries(boundaries, data) {
|
|
|
1314
1363
|
});
|
|
1315
1364
|
}
|
|
1316
1365
|
|
|
1366
|
+
// src/constraints/sharing/types.ts
|
|
1367
|
+
var import_zod = require("zod");
|
|
1368
|
+
var ManifestSchema = import_zod.z.object({
|
|
1369
|
+
name: import_zod.z.string(),
|
|
1370
|
+
version: import_zod.z.string(),
|
|
1371
|
+
description: import_zod.z.string().optional(),
|
|
1372
|
+
include: import_zod.z.array(import_zod.z.string()).min(1),
|
|
1373
|
+
minHarnessVersion: import_zod.z.string().optional(),
|
|
1374
|
+
keywords: import_zod.z.array(import_zod.z.string()).optional().default([]),
|
|
1375
|
+
layers: import_zod.z.record(import_zod.z.array(import_zod.z.string())).optional(),
|
|
1376
|
+
boundaries: import_zod.z.array(
|
|
1377
|
+
import_zod.z.object({
|
|
1378
|
+
name: import_zod.z.string(),
|
|
1379
|
+
layer: import_zod.z.string(),
|
|
1380
|
+
direction: import_zod.z.enum(["input", "output"]),
|
|
1381
|
+
schema: import_zod.z.string()
|
|
1382
|
+
})
|
|
1383
|
+
).optional()
|
|
1384
|
+
});
|
|
1385
|
+
var BundleConstraintsSchema = import_zod.z.object({
|
|
1386
|
+
layers: import_zod.z.array(
|
|
1387
|
+
import_zod.z.object({
|
|
1388
|
+
name: import_zod.z.string(),
|
|
1389
|
+
pattern: import_zod.z.string(),
|
|
1390
|
+
allowedDependencies: import_zod.z.array(import_zod.z.string())
|
|
1391
|
+
})
|
|
1392
|
+
).optional(),
|
|
1393
|
+
forbiddenImports: import_zod.z.array(
|
|
1394
|
+
import_zod.z.object({
|
|
1395
|
+
from: import_zod.z.string(),
|
|
1396
|
+
disallow: import_zod.z.array(import_zod.z.string()),
|
|
1397
|
+
message: import_zod.z.string().optional()
|
|
1398
|
+
})
|
|
1399
|
+
).optional(),
|
|
1400
|
+
boundaries: import_zod.z.object({
|
|
1401
|
+
requireSchema: import_zod.z.array(import_zod.z.string()).optional()
|
|
1402
|
+
}).optional(),
|
|
1403
|
+
architecture: import_zod.z.object({
|
|
1404
|
+
thresholds: import_zod.z.record(import_zod.z.unknown()).optional(),
|
|
1405
|
+
modules: import_zod.z.record(import_zod.z.record(import_zod.z.unknown())).optional()
|
|
1406
|
+
}).optional(),
|
|
1407
|
+
security: import_zod.z.object({
|
|
1408
|
+
rules: import_zod.z.record(import_zod.z.string()).optional()
|
|
1409
|
+
}).optional()
|
|
1410
|
+
});
|
|
1411
|
+
var BundleSchema = import_zod.z.object({
|
|
1412
|
+
name: import_zod.z.string(),
|
|
1413
|
+
version: import_zod.z.string(),
|
|
1414
|
+
description: import_zod.z.string().optional(),
|
|
1415
|
+
minHarnessVersion: import_zod.z.string().optional(),
|
|
1416
|
+
manifest: ManifestSchema,
|
|
1417
|
+
constraints: BundleConstraintsSchema,
|
|
1418
|
+
contributions: import_zod.z.record(import_zod.z.unknown()).optional()
|
|
1419
|
+
});
|
|
1420
|
+
var ContributionsSchema = import_zod.z.record(import_zod.z.unknown());
|
|
1421
|
+
var LockfilePackageSchema = import_zod.z.object({
|
|
1422
|
+
version: import_zod.z.string(),
|
|
1423
|
+
source: import_zod.z.string(),
|
|
1424
|
+
installedAt: import_zod.z.string(),
|
|
1425
|
+
contributions: import_zod.z.record(import_zod.z.unknown()).optional().nullable(),
|
|
1426
|
+
resolved: import_zod.z.string().optional(),
|
|
1427
|
+
integrity: import_zod.z.string().optional(),
|
|
1428
|
+
provenance: import_zod.z.array(import_zod.z.string()).optional()
|
|
1429
|
+
});
|
|
1430
|
+
var LockfileSchema = import_zod.z.object({
|
|
1431
|
+
version: import_zod.z.literal(1).optional(),
|
|
1432
|
+
// Used by some tests
|
|
1433
|
+
lockfileVersion: import_zod.z.literal(1).optional(),
|
|
1434
|
+
// Standard field
|
|
1435
|
+
packages: import_zod.z.record(LockfilePackageSchema)
|
|
1436
|
+
}).refine((data) => data.version !== void 0 || data.lockfileVersion !== void 0, {
|
|
1437
|
+
message: "Either 'version' or 'lockfileVersion' must be present and equal to 1"
|
|
1438
|
+
});
|
|
1439
|
+
var SharableLayerSchema = import_zod.z.unknown();
|
|
1440
|
+
var SharableForbiddenImportSchema = import_zod.z.unknown();
|
|
1441
|
+
var SharableBoundaryConfigSchema = import_zod.z.unknown();
|
|
1442
|
+
var SharableSecurityRulesSchema = import_zod.z.unknown();
|
|
1443
|
+
|
|
1444
|
+
// src/constraints/sharing/write-config.ts
|
|
1445
|
+
var fs = __toESM(require("fs/promises"));
|
|
1446
|
+
async function writeConfig(filePath, content) {
|
|
1447
|
+
try {
|
|
1448
|
+
const json = JSON.stringify(content, null, 2) + "\n";
|
|
1449
|
+
await fs.writeFile(filePath, json, "utf-8");
|
|
1450
|
+
return (0, import_types.Ok)(void 0);
|
|
1451
|
+
} catch (error) {
|
|
1452
|
+
return (0, import_types.Err)(error instanceof Error ? error : new Error(String(error)));
|
|
1453
|
+
}
|
|
1454
|
+
}
|
|
1455
|
+
|
|
1456
|
+
// src/constraints/sharing/manifest.ts
|
|
1457
|
+
function parseManifest(parsed) {
|
|
1458
|
+
const result = ManifestSchema.safeParse(parsed);
|
|
1459
|
+
if (!result.success) {
|
|
1460
|
+
const issues = result.error.issues.map((i) => `${i.path.join(".") || "(root)"}: ${i.message}`).join("; ");
|
|
1461
|
+
return { ok: false, error: `Invalid manifest: ${issues}` };
|
|
1462
|
+
}
|
|
1463
|
+
return { ok: true, value: result.data };
|
|
1464
|
+
}
|
|
1465
|
+
|
|
1466
|
+
// src/constraints/sharing/bundle.ts
|
|
1467
|
+
function resolveDotPath(obj, dotPath) {
|
|
1468
|
+
const segments = dotPath.split(".");
|
|
1469
|
+
let current = obj;
|
|
1470
|
+
for (const segment of segments) {
|
|
1471
|
+
if (current === null || typeof current !== "object") {
|
|
1472
|
+
return void 0;
|
|
1473
|
+
}
|
|
1474
|
+
current = current[segment];
|
|
1475
|
+
}
|
|
1476
|
+
return current;
|
|
1477
|
+
}
|
|
1478
|
+
function setDotPath(obj, dotPath, value) {
|
|
1479
|
+
const segments = dotPath.split(".");
|
|
1480
|
+
const lastSegment = segments[segments.length - 1];
|
|
1481
|
+
const parentSegments = segments.slice(0, -1);
|
|
1482
|
+
let current = obj;
|
|
1483
|
+
for (const segment of parentSegments) {
|
|
1484
|
+
if (current[segment] === void 0 || current[segment] === null || typeof current[segment] !== "object") {
|
|
1485
|
+
current[segment] = {};
|
|
1486
|
+
}
|
|
1487
|
+
current = current[segment];
|
|
1488
|
+
}
|
|
1489
|
+
if (lastSegment !== void 0) {
|
|
1490
|
+
current[lastSegment] = value;
|
|
1491
|
+
}
|
|
1492
|
+
}
|
|
1493
|
+
function extractBundle(manifest, config) {
|
|
1494
|
+
const constraints = {};
|
|
1495
|
+
for (const includePath of manifest.include) {
|
|
1496
|
+
const value = resolveDotPath(config, includePath);
|
|
1497
|
+
if (value !== void 0) {
|
|
1498
|
+
setDotPath(constraints, includePath, value);
|
|
1499
|
+
}
|
|
1500
|
+
}
|
|
1501
|
+
const bundle = {
|
|
1502
|
+
name: manifest.name,
|
|
1503
|
+
version: manifest.version,
|
|
1504
|
+
...manifest.minHarnessVersion !== void 0 && {
|
|
1505
|
+
minHarnessVersion: manifest.minHarnessVersion
|
|
1506
|
+
},
|
|
1507
|
+
...manifest.description !== void 0 && {
|
|
1508
|
+
description: manifest.description
|
|
1509
|
+
},
|
|
1510
|
+
manifest,
|
|
1511
|
+
constraints
|
|
1512
|
+
};
|
|
1513
|
+
const parsed = BundleSchema.safeParse(bundle);
|
|
1514
|
+
if (!parsed.success) {
|
|
1515
|
+
const issues = parsed.error.issues.map((i) => `${i.path.join(".") || "(root)"}: ${i.message}`).join("; ");
|
|
1516
|
+
return { ok: false, error: `Invalid bundle: ${issues}` };
|
|
1517
|
+
}
|
|
1518
|
+
return { ok: true, value: parsed.data };
|
|
1519
|
+
}
|
|
1520
|
+
|
|
1521
|
+
// src/constraints/sharing/merge.ts
|
|
1522
|
+
function deepEqual(a, b) {
|
|
1523
|
+
if (a === b) return true;
|
|
1524
|
+
if (typeof a !== typeof b) return false;
|
|
1525
|
+
if (typeof a !== "object" || a === null || b === null) return false;
|
|
1526
|
+
if (Array.isArray(a) && Array.isArray(b)) {
|
|
1527
|
+
if (a.length !== b.length) return false;
|
|
1528
|
+
return a.every((val, i) => deepEqual(val, b[i]));
|
|
1529
|
+
}
|
|
1530
|
+
if (Array.isArray(a) !== Array.isArray(b)) return false;
|
|
1531
|
+
const keysA = Object.keys(a);
|
|
1532
|
+
const keysB = Object.keys(b);
|
|
1533
|
+
if (keysA.length !== keysB.length) return false;
|
|
1534
|
+
return keysA.every(
|
|
1535
|
+
(key) => deepEqual(a[key], b[key])
|
|
1536
|
+
);
|
|
1537
|
+
}
|
|
1538
|
+
function stringArraysEqual(a, b) {
|
|
1539
|
+
if (a.length !== b.length) return false;
|
|
1540
|
+
const sortedA = [...a].sort();
|
|
1541
|
+
const sortedB = [...b].sort();
|
|
1542
|
+
return sortedA.every((val, i) => val === sortedB[i]);
|
|
1543
|
+
}
|
|
1544
|
+
function deepMergeConstraints(localConfig, bundleConstraints, _existingContributions) {
|
|
1545
|
+
const config = { ...localConfig };
|
|
1546
|
+
const contributions = {};
|
|
1547
|
+
const conflicts = [];
|
|
1548
|
+
if (bundleConstraints.layers && bundleConstraints.layers.length > 0) {
|
|
1549
|
+
const localLayers = Array.isArray(localConfig.layers) ? localConfig.layers : [];
|
|
1550
|
+
const mergedLayers = [...localLayers];
|
|
1551
|
+
const contributedLayerNames = [];
|
|
1552
|
+
for (const bundleLayer of bundleConstraints.layers) {
|
|
1553
|
+
const existing = localLayers.find((l) => l.name === bundleLayer.name);
|
|
1554
|
+
if (!existing) {
|
|
1555
|
+
mergedLayers.push(bundleLayer);
|
|
1556
|
+
contributedLayerNames.push(bundleLayer.name);
|
|
1557
|
+
} else {
|
|
1558
|
+
const same = existing.pattern === bundleLayer.pattern && stringArraysEqual(existing.allowedDependencies, bundleLayer.allowedDependencies);
|
|
1559
|
+
if (!same) {
|
|
1560
|
+
conflicts.push({
|
|
1561
|
+
section: "layers",
|
|
1562
|
+
key: bundleLayer.name,
|
|
1563
|
+
localValue: existing,
|
|
1564
|
+
packageValue: bundleLayer,
|
|
1565
|
+
description: `Layer '${bundleLayer.name}' already exists locally with different configuration`
|
|
1566
|
+
});
|
|
1567
|
+
}
|
|
1568
|
+
}
|
|
1569
|
+
}
|
|
1570
|
+
config.layers = mergedLayers;
|
|
1571
|
+
if (contributedLayerNames.length > 0) {
|
|
1572
|
+
contributions.layers = contributedLayerNames;
|
|
1573
|
+
}
|
|
1574
|
+
}
|
|
1575
|
+
if (bundleConstraints.forbiddenImports && bundleConstraints.forbiddenImports.length > 0) {
|
|
1576
|
+
const localFI = Array.isArray(localConfig.forbiddenImports) ? localConfig.forbiddenImports : [];
|
|
1577
|
+
const mergedFI = [...localFI];
|
|
1578
|
+
const contributedFromKeys = [];
|
|
1579
|
+
for (const bundleRule of bundleConstraints.forbiddenImports) {
|
|
1580
|
+
const existing = localFI.find((r) => r.from === bundleRule.from);
|
|
1581
|
+
if (!existing) {
|
|
1582
|
+
const entry = {
|
|
1583
|
+
from: bundleRule.from,
|
|
1584
|
+
disallow: bundleRule.disallow
|
|
1585
|
+
};
|
|
1586
|
+
if (bundleRule.message !== void 0) {
|
|
1587
|
+
entry.message = bundleRule.message;
|
|
1588
|
+
}
|
|
1589
|
+
mergedFI.push(entry);
|
|
1590
|
+
contributedFromKeys.push(bundleRule.from);
|
|
1591
|
+
} else {
|
|
1592
|
+
const same = stringArraysEqual(existing.disallow, bundleRule.disallow);
|
|
1593
|
+
if (!same) {
|
|
1594
|
+
conflicts.push({
|
|
1595
|
+
section: "forbiddenImports",
|
|
1596
|
+
key: bundleRule.from,
|
|
1597
|
+
localValue: existing,
|
|
1598
|
+
packageValue: bundleRule,
|
|
1599
|
+
description: `Forbidden import rule for '${bundleRule.from}' already exists locally with different disallow list`
|
|
1600
|
+
});
|
|
1601
|
+
}
|
|
1602
|
+
}
|
|
1603
|
+
}
|
|
1604
|
+
config.forbiddenImports = mergedFI;
|
|
1605
|
+
if (contributedFromKeys.length > 0) {
|
|
1606
|
+
contributions.forbiddenImports = contributedFromKeys;
|
|
1607
|
+
}
|
|
1608
|
+
}
|
|
1609
|
+
if (bundleConstraints.boundaries) {
|
|
1610
|
+
const localBoundaries = localConfig.boundaries ?? { requireSchema: [] };
|
|
1611
|
+
const localSchemas = new Set(localBoundaries.requireSchema ?? []);
|
|
1612
|
+
const bundleSchemas = bundleConstraints.boundaries.requireSchema ?? [];
|
|
1613
|
+
const newSchemas = [];
|
|
1614
|
+
for (const schema of bundleSchemas) {
|
|
1615
|
+
if (!localSchemas.has(schema)) {
|
|
1616
|
+
newSchemas.push(schema);
|
|
1617
|
+
localSchemas.add(schema);
|
|
1618
|
+
}
|
|
1619
|
+
}
|
|
1620
|
+
config.boundaries = {
|
|
1621
|
+
requireSchema: [...localBoundaries.requireSchema ?? [], ...newSchemas]
|
|
1622
|
+
};
|
|
1623
|
+
if (newSchemas.length > 0) {
|
|
1624
|
+
contributions.boundaries = newSchemas;
|
|
1625
|
+
}
|
|
1626
|
+
}
|
|
1627
|
+
if (bundleConstraints.architecture) {
|
|
1628
|
+
const localArch = localConfig.architecture ?? {
|
|
1629
|
+
enabled: true,
|
|
1630
|
+
baselinePath: ".harness/arch/baselines.json",
|
|
1631
|
+
thresholds: {},
|
|
1632
|
+
modules: {}
|
|
1633
|
+
};
|
|
1634
|
+
const mergedThresholds = { ...localArch.thresholds };
|
|
1635
|
+
const contributedThresholdKeys = [];
|
|
1636
|
+
const bundleThresholds = bundleConstraints.architecture.thresholds ?? {};
|
|
1637
|
+
for (const [category, value] of Object.entries(bundleThresholds)) {
|
|
1638
|
+
if (!(category in mergedThresholds)) {
|
|
1639
|
+
mergedThresholds[category] = value;
|
|
1640
|
+
contributedThresholdKeys.push(category);
|
|
1641
|
+
} else if (!deepEqual(mergedThresholds[category], value)) {
|
|
1642
|
+
conflicts.push({
|
|
1643
|
+
section: "architecture.thresholds",
|
|
1644
|
+
key: category,
|
|
1645
|
+
localValue: mergedThresholds[category],
|
|
1646
|
+
packageValue: value,
|
|
1647
|
+
description: `Architecture threshold '${category}' already exists locally with a different value`
|
|
1648
|
+
});
|
|
1649
|
+
}
|
|
1650
|
+
}
|
|
1651
|
+
const mergedModules = { ...localArch.modules };
|
|
1652
|
+
const contributedModuleKeys = [];
|
|
1653
|
+
const bundleModules = bundleConstraints.architecture.modules ?? {};
|
|
1654
|
+
for (const [modulePath, bundleCategoryMap] of Object.entries(bundleModules)) {
|
|
1655
|
+
if (!(modulePath in mergedModules)) {
|
|
1656
|
+
mergedModules[modulePath] = bundleCategoryMap;
|
|
1657
|
+
for (const cat of Object.keys(bundleCategoryMap)) {
|
|
1658
|
+
contributedModuleKeys.push(`${modulePath}:${cat}`);
|
|
1659
|
+
}
|
|
1660
|
+
} else {
|
|
1661
|
+
const localCategoryMap = mergedModules[modulePath];
|
|
1662
|
+
const mergedCategoryMap = { ...localCategoryMap };
|
|
1663
|
+
for (const [category, value] of Object.entries(bundleCategoryMap)) {
|
|
1664
|
+
if (!(category in mergedCategoryMap)) {
|
|
1665
|
+
mergedCategoryMap[category] = value;
|
|
1666
|
+
contributedModuleKeys.push(`${modulePath}:${category}`);
|
|
1667
|
+
} else if (!deepEqual(mergedCategoryMap[category], value)) {
|
|
1668
|
+
conflicts.push({
|
|
1669
|
+
section: "architecture.modules",
|
|
1670
|
+
key: `${modulePath}:${category}`,
|
|
1671
|
+
localValue: mergedCategoryMap[category],
|
|
1672
|
+
packageValue: value,
|
|
1673
|
+
description: `Architecture module override '${modulePath}' category '${category}' already exists locally with a different value`
|
|
1674
|
+
});
|
|
1675
|
+
}
|
|
1676
|
+
}
|
|
1677
|
+
mergedModules[modulePath] = mergedCategoryMap;
|
|
1678
|
+
}
|
|
1679
|
+
}
|
|
1680
|
+
config.architecture = {
|
|
1681
|
+
...localArch,
|
|
1682
|
+
thresholds: mergedThresholds,
|
|
1683
|
+
modules: mergedModules
|
|
1684
|
+
};
|
|
1685
|
+
if (contributedThresholdKeys.length > 0) {
|
|
1686
|
+
contributions["architecture.thresholds"] = contributedThresholdKeys;
|
|
1687
|
+
}
|
|
1688
|
+
if (contributedModuleKeys.length > 0) {
|
|
1689
|
+
contributions["architecture.modules"] = contributedModuleKeys;
|
|
1690
|
+
}
|
|
1691
|
+
}
|
|
1692
|
+
if (bundleConstraints.security?.rules) {
|
|
1693
|
+
const localSecurity = localConfig.security ?? { rules: {} };
|
|
1694
|
+
const localRules = localSecurity.rules ?? {};
|
|
1695
|
+
const mergedRules = { ...localRules };
|
|
1696
|
+
const contributedRuleIds = [];
|
|
1697
|
+
for (const [ruleId, severity] of Object.entries(bundleConstraints.security.rules)) {
|
|
1698
|
+
if (!(ruleId in mergedRules)) {
|
|
1699
|
+
mergedRules[ruleId] = severity;
|
|
1700
|
+
contributedRuleIds.push(ruleId);
|
|
1701
|
+
} else if (mergedRules[ruleId] !== severity) {
|
|
1702
|
+
conflicts.push({
|
|
1703
|
+
section: "security.rules",
|
|
1704
|
+
key: ruleId,
|
|
1705
|
+
localValue: mergedRules[ruleId],
|
|
1706
|
+
packageValue: severity,
|
|
1707
|
+
description: `Security rule '${ruleId}' already exists locally with severity '${mergedRules[ruleId]}', bundle has '${severity}'`
|
|
1708
|
+
});
|
|
1709
|
+
}
|
|
1710
|
+
}
|
|
1711
|
+
config.security = { ...localSecurity, rules: mergedRules };
|
|
1712
|
+
if (contributedRuleIds.length > 0) {
|
|
1713
|
+
contributions["security.rules"] = contributedRuleIds;
|
|
1714
|
+
}
|
|
1715
|
+
}
|
|
1716
|
+
return { config, contributions, conflicts };
|
|
1717
|
+
}
|
|
1718
|
+
|
|
1719
|
+
// src/constraints/sharing/lockfile.ts
|
|
1720
|
+
var fs2 = __toESM(require("fs/promises"));
|
|
1721
|
+
async function readLockfile(lockfilePath) {
|
|
1722
|
+
let raw;
|
|
1723
|
+
try {
|
|
1724
|
+
raw = await fs2.readFile(lockfilePath, "utf-8");
|
|
1725
|
+
} catch (err) {
|
|
1726
|
+
if (isNodeError(err) && err.code === "ENOENT") {
|
|
1727
|
+
return { ok: true, value: null };
|
|
1728
|
+
}
|
|
1729
|
+
return {
|
|
1730
|
+
ok: false,
|
|
1731
|
+
error: `Failed to read lockfile: ${err instanceof Error ? err.message : String(err)}`
|
|
1732
|
+
};
|
|
1733
|
+
}
|
|
1734
|
+
let parsed;
|
|
1735
|
+
try {
|
|
1736
|
+
parsed = JSON.parse(raw);
|
|
1737
|
+
} catch {
|
|
1738
|
+
return {
|
|
1739
|
+
ok: false,
|
|
1740
|
+
error: `Failed to parse lockfile as JSON: file contains invalid JSON`
|
|
1741
|
+
};
|
|
1742
|
+
}
|
|
1743
|
+
const result = LockfileSchema.safeParse(parsed);
|
|
1744
|
+
if (!result.success) {
|
|
1745
|
+
return {
|
|
1746
|
+
ok: false,
|
|
1747
|
+
error: `Lockfile schema validation failed: ${result.error.issues.map((i) => i.message).join(", ")}`
|
|
1748
|
+
};
|
|
1749
|
+
}
|
|
1750
|
+
return { ok: true, value: result.data };
|
|
1751
|
+
}
|
|
1752
|
+
async function writeLockfile(lockfilePath, lockfile) {
|
|
1753
|
+
await writeConfig(lockfilePath, lockfile);
|
|
1754
|
+
}
|
|
1755
|
+
function addProvenance(lockfile, packageName, entry) {
|
|
1756
|
+
return {
|
|
1757
|
+
...lockfile,
|
|
1758
|
+
packages: {
|
|
1759
|
+
...lockfile.packages,
|
|
1760
|
+
[packageName]: entry
|
|
1761
|
+
}
|
|
1762
|
+
};
|
|
1763
|
+
}
|
|
1764
|
+
function removeProvenance(lockfile, packageName) {
|
|
1765
|
+
const existing = lockfile.packages[packageName];
|
|
1766
|
+
if (!existing) {
|
|
1767
|
+
return { lockfile, contributions: null };
|
|
1768
|
+
}
|
|
1769
|
+
const { [packageName]: _removed, ...remaining } = lockfile.packages;
|
|
1770
|
+
return {
|
|
1771
|
+
lockfile: {
|
|
1772
|
+
...lockfile,
|
|
1773
|
+
packages: remaining
|
|
1774
|
+
},
|
|
1775
|
+
contributions: existing.contributions ?? null
|
|
1776
|
+
};
|
|
1777
|
+
}
|
|
1778
|
+
function isNodeError(err) {
|
|
1779
|
+
return err instanceof Error && "code" in err;
|
|
1780
|
+
}
|
|
1781
|
+
|
|
1317
1782
|
// src/shared/parsers/typescript.ts
|
|
1318
1783
|
var import_typescript_estree = require("@typescript-eslint/typescript-estree");
|
|
1319
1784
|
|
|
@@ -1339,11 +1804,11 @@ function walk(node, visitor) {
|
|
|
1339
1804
|
var TypeScriptParser = class {
|
|
1340
1805
|
name = "typescript";
|
|
1341
1806
|
extensions = [".ts", ".tsx", ".mts", ".cts"];
|
|
1342
|
-
async parseFile(
|
|
1343
|
-
const contentResult = await readFileContent(
|
|
1807
|
+
async parseFile(path13) {
|
|
1808
|
+
const contentResult = await readFileContent(path13);
|
|
1344
1809
|
if (!contentResult.ok) {
|
|
1345
1810
|
return (0, import_types.Err)(
|
|
1346
|
-
createParseError("NOT_FOUND", `File not found: ${
|
|
1811
|
+
createParseError("NOT_FOUND", `File not found: ${path13}`, { path: path13 }, [
|
|
1347
1812
|
"Check that the file exists",
|
|
1348
1813
|
"Verify the path is correct"
|
|
1349
1814
|
])
|
|
@@ -1353,7 +1818,7 @@ var TypeScriptParser = class {
|
|
|
1353
1818
|
const ast = (0, import_typescript_estree.parse)(contentResult.value, {
|
|
1354
1819
|
loc: true,
|
|
1355
1820
|
range: true,
|
|
1356
|
-
jsx:
|
|
1821
|
+
jsx: path13.endsWith(".tsx"),
|
|
1357
1822
|
errorOnUnknownASTType: false
|
|
1358
1823
|
});
|
|
1359
1824
|
return (0, import_types.Ok)({
|
|
@@ -1364,7 +1829,7 @@ var TypeScriptParser = class {
|
|
|
1364
1829
|
} catch (e) {
|
|
1365
1830
|
const error = e;
|
|
1366
1831
|
return (0, import_types.Err)(
|
|
1367
|
-
createParseError("SYNTAX_ERROR", `Failed to parse ${
|
|
1832
|
+
createParseError("SYNTAX_ERROR", `Failed to parse ${path13}: ${error.message}`, { path: path13 }, [
|
|
1368
1833
|
"Check for syntax errors in the file",
|
|
1369
1834
|
"Ensure valid TypeScript syntax"
|
|
1370
1835
|
])
|
|
@@ -1531,7 +1996,7 @@ var TypeScriptParser = class {
|
|
|
1531
1996
|
|
|
1532
1997
|
// src/entropy/snapshot.ts
|
|
1533
1998
|
var import_path6 = require("path");
|
|
1534
|
-
var
|
|
1999
|
+
var import_minimatch3 = require("minimatch");
|
|
1535
2000
|
async function resolveEntryPoints(rootDir, explicitEntries) {
|
|
1536
2001
|
if (explicitEntries && explicitEntries.length > 0) {
|
|
1537
2002
|
const resolved = explicitEntries.map((e) => (0, import_path6.resolve)(rootDir, e));
|
|
@@ -1648,22 +2113,22 @@ function extractInlineRefs(content) {
|
|
|
1648
2113
|
}
|
|
1649
2114
|
return refs;
|
|
1650
2115
|
}
|
|
1651
|
-
async function parseDocumentationFile(
|
|
1652
|
-
const contentResult = await readFileContent(
|
|
2116
|
+
async function parseDocumentationFile(path13) {
|
|
2117
|
+
const contentResult = await readFileContent(path13);
|
|
1653
2118
|
if (!contentResult.ok) {
|
|
1654
2119
|
return (0, import_types.Err)(
|
|
1655
2120
|
createEntropyError(
|
|
1656
2121
|
"PARSE_ERROR",
|
|
1657
|
-
`Failed to read documentation file: ${
|
|
1658
|
-
{ file:
|
|
2122
|
+
`Failed to read documentation file: ${path13}`,
|
|
2123
|
+
{ file: path13 },
|
|
1659
2124
|
["Check that the file exists"]
|
|
1660
2125
|
)
|
|
1661
2126
|
);
|
|
1662
2127
|
}
|
|
1663
2128
|
const content = contentResult.value;
|
|
1664
|
-
const type =
|
|
2129
|
+
const type = path13.endsWith(".md") ? "markdown" : "text";
|
|
1665
2130
|
return (0, import_types.Ok)({
|
|
1666
|
-
path:
|
|
2131
|
+
path: path13,
|
|
1667
2132
|
type,
|
|
1668
2133
|
content,
|
|
1669
2134
|
codeBlocks: extractCodeBlocks(content),
|
|
@@ -1795,7 +2260,7 @@ async function buildSnapshot(config) {
|
|
|
1795
2260
|
}
|
|
1796
2261
|
sourceFilePaths = sourceFilePaths.filter((f) => {
|
|
1797
2262
|
const rel = (0, import_path6.relative)(rootDir, f);
|
|
1798
|
-
return !excludePatterns.some((p) => (0,
|
|
2263
|
+
return !excludePatterns.some((p) => (0, import_minimatch3.minimatch)(rel, p));
|
|
1799
2264
|
});
|
|
1800
2265
|
const files = [];
|
|
1801
2266
|
for (const filePath of sourceFilePaths) {
|
|
@@ -2325,11 +2790,11 @@ async function detectDeadCode(snapshot, graphDeadCodeData) {
|
|
|
2325
2790
|
}
|
|
2326
2791
|
|
|
2327
2792
|
// src/entropy/detectors/patterns.ts
|
|
2328
|
-
var
|
|
2793
|
+
var import_minimatch4 = require("minimatch");
|
|
2329
2794
|
var import_path9 = require("path");
|
|
2330
2795
|
function fileMatchesPattern(filePath, pattern, rootDir) {
|
|
2331
2796
|
const relativePath = (0, import_path9.relative)(rootDir, filePath);
|
|
2332
|
-
return (0,
|
|
2797
|
+
return (0, import_minimatch4.minimatch)(relativePath, pattern);
|
|
2333
2798
|
}
|
|
2334
2799
|
function checkConfigPattern(pattern, file, rootDir) {
|
|
2335
2800
|
const matches = [];
|
|
@@ -3331,14 +3796,14 @@ var EntropyAnalyzer = class {
|
|
|
3331
3796
|
};
|
|
3332
3797
|
|
|
3333
3798
|
// src/entropy/fixers/safe-fixes.ts
|
|
3334
|
-
var
|
|
3799
|
+
var fs3 = __toESM(require("fs"));
|
|
3335
3800
|
var import_util2 = require("util");
|
|
3336
3801
|
var import_path10 = require("path");
|
|
3337
|
-
var
|
|
3338
|
-
var
|
|
3339
|
-
var unlink2 = (0, import_util2.promisify)(
|
|
3340
|
-
var mkdir2 = (0, import_util2.promisify)(
|
|
3341
|
-
var copyFile2 = (0, import_util2.promisify)(
|
|
3802
|
+
var readFile5 = (0, import_util2.promisify)(fs3.readFile);
|
|
3803
|
+
var writeFile3 = (0, import_util2.promisify)(fs3.writeFile);
|
|
3804
|
+
var unlink2 = (0, import_util2.promisify)(fs3.unlink);
|
|
3805
|
+
var mkdir2 = (0, import_util2.promisify)(fs3.mkdir);
|
|
3806
|
+
var copyFile2 = (0, import_util2.promisify)(fs3.copyFile);
|
|
3342
3807
|
var DEFAULT_FIX_CONFIG = {
|
|
3343
3808
|
dryRun: false,
|
|
3344
3809
|
fixTypes: ["unused-imports", "dead-files"],
|
|
@@ -3465,25 +3930,25 @@ async function applySingleFix(fix, config) {
|
|
|
3465
3930
|
break;
|
|
3466
3931
|
case "delete-lines":
|
|
3467
3932
|
if (fix.line !== void 0) {
|
|
3468
|
-
const content = await
|
|
3933
|
+
const content = await readFile5(fix.file, "utf-8");
|
|
3469
3934
|
const lines = content.split("\n");
|
|
3470
3935
|
lines.splice(fix.line - 1, 1);
|
|
3471
|
-
await
|
|
3936
|
+
await writeFile3(fix.file, lines.join("\n"));
|
|
3472
3937
|
}
|
|
3473
3938
|
break;
|
|
3474
3939
|
case "replace":
|
|
3475
3940
|
if (fix.oldContent && fix.newContent !== void 0) {
|
|
3476
|
-
const content = await
|
|
3941
|
+
const content = await readFile5(fix.file, "utf-8");
|
|
3477
3942
|
const newContent = content.replace(fix.oldContent, fix.newContent);
|
|
3478
|
-
await
|
|
3943
|
+
await writeFile3(fix.file, newContent);
|
|
3479
3944
|
}
|
|
3480
3945
|
break;
|
|
3481
3946
|
case "insert":
|
|
3482
3947
|
if (fix.line !== void 0 && fix.newContent) {
|
|
3483
|
-
const content = await
|
|
3948
|
+
const content = await readFile5(fix.file, "utf-8");
|
|
3484
3949
|
const lines = content.split("\n");
|
|
3485
3950
|
lines.splice(fix.line - 1, 0, fix.newContent);
|
|
3486
|
-
await
|
|
3951
|
+
await writeFile3(fix.file, lines.join("\n"));
|
|
3487
3952
|
}
|
|
3488
3953
|
break;
|
|
3489
3954
|
}
|
|
@@ -3660,46 +4125,46 @@ function deduplicateCleanupFindings(findings) {
|
|
|
3660
4125
|
}
|
|
3661
4126
|
|
|
3662
4127
|
// src/entropy/config/schema.ts
|
|
3663
|
-
var
|
|
3664
|
-
var MustExportRuleSchema =
|
|
3665
|
-
type:
|
|
3666
|
-
names:
|
|
4128
|
+
var import_zod2 = require("zod");
|
|
4129
|
+
var MustExportRuleSchema = import_zod2.z.object({
|
|
4130
|
+
type: import_zod2.z.literal("must-export"),
|
|
4131
|
+
names: import_zod2.z.array(import_zod2.z.string())
|
|
3667
4132
|
});
|
|
3668
|
-
var MustExportDefaultRuleSchema =
|
|
3669
|
-
type:
|
|
3670
|
-
kind:
|
|
4133
|
+
var MustExportDefaultRuleSchema = import_zod2.z.object({
|
|
4134
|
+
type: import_zod2.z.literal("must-export-default"),
|
|
4135
|
+
kind: import_zod2.z.enum(["class", "function", "object"]).optional()
|
|
3671
4136
|
});
|
|
3672
|
-
var NoExportRuleSchema =
|
|
3673
|
-
type:
|
|
3674
|
-
names:
|
|
4137
|
+
var NoExportRuleSchema = import_zod2.z.object({
|
|
4138
|
+
type: import_zod2.z.literal("no-export"),
|
|
4139
|
+
names: import_zod2.z.array(import_zod2.z.string())
|
|
3675
4140
|
});
|
|
3676
|
-
var MustImportRuleSchema =
|
|
3677
|
-
type:
|
|
3678
|
-
from:
|
|
3679
|
-
names:
|
|
4141
|
+
var MustImportRuleSchema = import_zod2.z.object({
|
|
4142
|
+
type: import_zod2.z.literal("must-import"),
|
|
4143
|
+
from: import_zod2.z.string(),
|
|
4144
|
+
names: import_zod2.z.array(import_zod2.z.string()).optional()
|
|
3680
4145
|
});
|
|
3681
|
-
var NoImportRuleSchema =
|
|
3682
|
-
type:
|
|
3683
|
-
from:
|
|
4146
|
+
var NoImportRuleSchema = import_zod2.z.object({
|
|
4147
|
+
type: import_zod2.z.literal("no-import"),
|
|
4148
|
+
from: import_zod2.z.string()
|
|
3684
4149
|
});
|
|
3685
|
-
var NamingRuleSchema =
|
|
3686
|
-
type:
|
|
3687
|
-
match:
|
|
3688
|
-
convention:
|
|
4150
|
+
var NamingRuleSchema = import_zod2.z.object({
|
|
4151
|
+
type: import_zod2.z.literal("naming"),
|
|
4152
|
+
match: import_zod2.z.string(),
|
|
4153
|
+
convention: import_zod2.z.enum(["camelCase", "PascalCase", "UPPER_SNAKE", "kebab-case"])
|
|
3689
4154
|
});
|
|
3690
|
-
var MaxExportsRuleSchema =
|
|
3691
|
-
type:
|
|
3692
|
-
count:
|
|
4155
|
+
var MaxExportsRuleSchema = import_zod2.z.object({
|
|
4156
|
+
type: import_zod2.z.literal("max-exports"),
|
|
4157
|
+
count: import_zod2.z.number().positive()
|
|
3693
4158
|
});
|
|
3694
|
-
var MaxLinesRuleSchema =
|
|
3695
|
-
type:
|
|
3696
|
-
count:
|
|
4159
|
+
var MaxLinesRuleSchema = import_zod2.z.object({
|
|
4160
|
+
type: import_zod2.z.literal("max-lines"),
|
|
4161
|
+
count: import_zod2.z.number().positive()
|
|
3697
4162
|
});
|
|
3698
|
-
var RequireJSDocRuleSchema =
|
|
3699
|
-
type:
|
|
3700
|
-
for:
|
|
4163
|
+
var RequireJSDocRuleSchema = import_zod2.z.object({
|
|
4164
|
+
type: import_zod2.z.literal("require-jsdoc"),
|
|
4165
|
+
for: import_zod2.z.array(import_zod2.z.enum(["function", "class", "export"]))
|
|
3701
4166
|
});
|
|
3702
|
-
var RuleSchema =
|
|
4167
|
+
var RuleSchema = import_zod2.z.discriminatedUnion("type", [
|
|
3703
4168
|
MustExportRuleSchema,
|
|
3704
4169
|
MustExportDefaultRuleSchema,
|
|
3705
4170
|
NoExportRuleSchema,
|
|
@@ -3710,47 +4175,47 @@ var RuleSchema = import_zod.z.discriminatedUnion("type", [
|
|
|
3710
4175
|
MaxLinesRuleSchema,
|
|
3711
4176
|
RequireJSDocRuleSchema
|
|
3712
4177
|
]);
|
|
3713
|
-
var ConfigPatternSchema =
|
|
3714
|
-
name:
|
|
3715
|
-
description:
|
|
3716
|
-
severity:
|
|
3717
|
-
files:
|
|
4178
|
+
var ConfigPatternSchema = import_zod2.z.object({
|
|
4179
|
+
name: import_zod2.z.string().min(1),
|
|
4180
|
+
description: import_zod2.z.string(),
|
|
4181
|
+
severity: import_zod2.z.enum(["error", "warning"]),
|
|
4182
|
+
files: import_zod2.z.array(import_zod2.z.string()),
|
|
3718
4183
|
rule: RuleSchema,
|
|
3719
|
-
message:
|
|
4184
|
+
message: import_zod2.z.string().optional()
|
|
3720
4185
|
});
|
|
3721
|
-
var PatternConfigSchema =
|
|
3722
|
-
patterns:
|
|
3723
|
-
customPatterns:
|
|
4186
|
+
var PatternConfigSchema = import_zod2.z.object({
|
|
4187
|
+
patterns: import_zod2.z.array(ConfigPatternSchema),
|
|
4188
|
+
customPatterns: import_zod2.z.array(import_zod2.z.any()).optional(),
|
|
3724
4189
|
// Code patterns are functions, can't validate
|
|
3725
|
-
ignoreFiles:
|
|
4190
|
+
ignoreFiles: import_zod2.z.array(import_zod2.z.string()).optional()
|
|
3726
4191
|
});
|
|
3727
|
-
var DriftConfigSchema =
|
|
3728
|
-
docPaths:
|
|
3729
|
-
checkApiSignatures:
|
|
3730
|
-
checkExamples:
|
|
3731
|
-
checkStructure:
|
|
3732
|
-
ignorePatterns:
|
|
4192
|
+
var DriftConfigSchema = import_zod2.z.object({
|
|
4193
|
+
docPaths: import_zod2.z.array(import_zod2.z.string()).optional(),
|
|
4194
|
+
checkApiSignatures: import_zod2.z.boolean().optional(),
|
|
4195
|
+
checkExamples: import_zod2.z.boolean().optional(),
|
|
4196
|
+
checkStructure: import_zod2.z.boolean().optional(),
|
|
4197
|
+
ignorePatterns: import_zod2.z.array(import_zod2.z.string()).optional()
|
|
3733
4198
|
});
|
|
3734
|
-
var DeadCodeConfigSchema =
|
|
3735
|
-
entryPoints:
|
|
3736
|
-
includeTypes:
|
|
3737
|
-
includeInternals:
|
|
3738
|
-
ignorePatterns:
|
|
3739
|
-
treatDynamicImportsAs:
|
|
4199
|
+
var DeadCodeConfigSchema = import_zod2.z.object({
|
|
4200
|
+
entryPoints: import_zod2.z.array(import_zod2.z.string()).optional(),
|
|
4201
|
+
includeTypes: import_zod2.z.boolean().optional(),
|
|
4202
|
+
includeInternals: import_zod2.z.boolean().optional(),
|
|
4203
|
+
ignorePatterns: import_zod2.z.array(import_zod2.z.string()).optional(),
|
|
4204
|
+
treatDynamicImportsAs: import_zod2.z.enum(["used", "unknown"]).optional()
|
|
3740
4205
|
});
|
|
3741
|
-
var EntropyConfigSchema =
|
|
3742
|
-
rootDir:
|
|
3743
|
-
parser:
|
|
4206
|
+
var EntropyConfigSchema = import_zod2.z.object({
|
|
4207
|
+
rootDir: import_zod2.z.string(),
|
|
4208
|
+
parser: import_zod2.z.any().optional(),
|
|
3744
4209
|
// LanguageParser instance, can't validate
|
|
3745
|
-
entryPoints:
|
|
3746
|
-
analyze:
|
|
3747
|
-
drift:
|
|
3748
|
-
deadCode:
|
|
3749
|
-
patterns:
|
|
4210
|
+
entryPoints: import_zod2.z.array(import_zod2.z.string()).optional(),
|
|
4211
|
+
analyze: import_zod2.z.object({
|
|
4212
|
+
drift: import_zod2.z.union([import_zod2.z.boolean(), DriftConfigSchema]).optional(),
|
|
4213
|
+
deadCode: import_zod2.z.union([import_zod2.z.boolean(), DeadCodeConfigSchema]).optional(),
|
|
4214
|
+
patterns: import_zod2.z.union([import_zod2.z.boolean(), PatternConfigSchema]).optional()
|
|
3750
4215
|
}),
|
|
3751
|
-
include:
|
|
3752
|
-
exclude:
|
|
3753
|
-
docPaths:
|
|
4216
|
+
include: import_zod2.z.array(import_zod2.z.string()).optional(),
|
|
4217
|
+
exclude: import_zod2.z.array(import_zod2.z.string()).optional(),
|
|
4218
|
+
docPaths: import_zod2.z.array(import_zod2.z.string()).optional()
|
|
3754
4219
|
});
|
|
3755
4220
|
function validatePatternConfig(config) {
|
|
3756
4221
|
const result = PatternConfigSchema.safeParse(config);
|
|
@@ -4007,7 +4472,7 @@ var RegressionDetector = class {
|
|
|
4007
4472
|
};
|
|
4008
4473
|
|
|
4009
4474
|
// src/performance/critical-path.ts
|
|
4010
|
-
var
|
|
4475
|
+
var fs4 = __toESM(require("fs"));
|
|
4011
4476
|
var path = __toESM(require("path"));
|
|
4012
4477
|
var SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", "dist", ".git"]);
|
|
4013
4478
|
var SOURCE_EXTENSIONS = /* @__PURE__ */ new Set([".ts", ".tsx", ".js", ".jsx"]);
|
|
@@ -4059,7 +4524,7 @@ var CriticalPathResolver = class {
|
|
|
4059
4524
|
walkDir(dir, entries) {
|
|
4060
4525
|
let items;
|
|
4061
4526
|
try {
|
|
4062
|
-
items =
|
|
4527
|
+
items = fs4.readdirSync(dir, { withFileTypes: true });
|
|
4063
4528
|
} catch {
|
|
4064
4529
|
return;
|
|
4065
4530
|
}
|
|
@@ -4075,7 +4540,7 @@ var CriticalPathResolver = class {
|
|
|
4075
4540
|
scanFile(filePath, entries) {
|
|
4076
4541
|
let content;
|
|
4077
4542
|
try {
|
|
4078
|
-
content =
|
|
4543
|
+
content = fs4.readFileSync(filePath, "utf-8");
|
|
4079
4544
|
} catch {
|
|
4080
4545
|
return;
|
|
4081
4546
|
}
|
|
@@ -4254,17 +4719,17 @@ function resetFeedbackConfig() {
|
|
|
4254
4719
|
}
|
|
4255
4720
|
|
|
4256
4721
|
// src/feedback/review/diff-analyzer.ts
|
|
4257
|
-
function parseDiff(
|
|
4722
|
+
function parseDiff(diff2) {
|
|
4258
4723
|
try {
|
|
4259
|
-
if (!
|
|
4260
|
-
return (0, import_types.Ok)({ diff, files: [] });
|
|
4724
|
+
if (!diff2.trim()) {
|
|
4725
|
+
return (0, import_types.Ok)({ diff: diff2, files: [] });
|
|
4261
4726
|
}
|
|
4262
4727
|
const files = [];
|
|
4263
4728
|
const newFileRegex = /new file mode/;
|
|
4264
4729
|
const deletedFileRegex = /deleted file mode/;
|
|
4265
4730
|
const additionRegex = /^\+(?!\+\+)/gm;
|
|
4266
4731
|
const deletionRegex = /^-(?!--)/gm;
|
|
4267
|
-
const diffParts =
|
|
4732
|
+
const diffParts = diff2.split(/(?=diff --git)/);
|
|
4268
4733
|
for (const part of diffParts) {
|
|
4269
4734
|
if (!part.trim()) continue;
|
|
4270
4735
|
const headerMatch = /diff --git a\/(.+?) b\/(.+?)(?:\n|$)/.exec(part);
|
|
@@ -4287,7 +4752,7 @@ function parseDiff(diff) {
|
|
|
4287
4752
|
deletions
|
|
4288
4753
|
});
|
|
4289
4754
|
}
|
|
4290
|
-
return (0, import_types.Ok)({ diff, files });
|
|
4755
|
+
return (0, import_types.Ok)({ diff: diff2, files });
|
|
4291
4756
|
} catch (error) {
|
|
4292
4757
|
return (0, import_types.Err)({
|
|
4293
4758
|
code: "DIFF_PARSE_ERROR",
|
|
@@ -4891,77 +5356,1265 @@ var NoOpSink = class {
|
|
|
4891
5356
|
}
|
|
4892
5357
|
};
|
|
4893
5358
|
|
|
5359
|
+
// src/architecture/types.ts
|
|
5360
|
+
var import_zod3 = require("zod");
|
|
5361
|
+
var ArchMetricCategorySchema = import_zod3.z.enum([
|
|
5362
|
+
"circular-deps",
|
|
5363
|
+
"layer-violations",
|
|
5364
|
+
"complexity",
|
|
5365
|
+
"coupling",
|
|
5366
|
+
"forbidden-imports",
|
|
5367
|
+
"module-size",
|
|
5368
|
+
"dependency-depth"
|
|
5369
|
+
]);
|
|
5370
|
+
var ViolationSchema = import_zod3.z.object({
|
|
5371
|
+
id: import_zod3.z.string(),
|
|
5372
|
+
// stable hash: sha256(relativePath + ':' + category + ':' + normalizedDetail)
|
|
5373
|
+
file: import_zod3.z.string(),
|
|
5374
|
+
// relative to project root
|
|
5375
|
+
category: ArchMetricCategorySchema.optional(),
|
|
5376
|
+
// context for baseline reporting
|
|
5377
|
+
detail: import_zod3.z.string(),
|
|
5378
|
+
// human-readable description
|
|
5379
|
+
severity: import_zod3.z.enum(["error", "warning"])
|
|
5380
|
+
});
|
|
5381
|
+
var MetricResultSchema = import_zod3.z.object({
|
|
5382
|
+
category: ArchMetricCategorySchema,
|
|
5383
|
+
scope: import_zod3.z.string(),
|
|
5384
|
+
// e.g., 'project', 'src/services', 'src/api/routes.ts'
|
|
5385
|
+
value: import_zod3.z.number(),
|
|
5386
|
+
// numeric metric (violation count, complexity score, etc.)
|
|
5387
|
+
violations: import_zod3.z.array(ViolationSchema),
|
|
5388
|
+
metadata: import_zod3.z.record(import_zod3.z.unknown()).optional()
|
|
5389
|
+
});
|
|
5390
|
+
var CategoryBaselineSchema = import_zod3.z.object({
|
|
5391
|
+
value: import_zod3.z.number(),
|
|
5392
|
+
// aggregate metric value at baseline time
|
|
5393
|
+
violationIds: import_zod3.z.array(import_zod3.z.string())
|
|
5394
|
+
// stable IDs of known violations (the allowlist)
|
|
5395
|
+
});
|
|
5396
|
+
var ArchBaselineSchema = import_zod3.z.object({
|
|
5397
|
+
version: import_zod3.z.literal(1),
|
|
5398
|
+
updatedAt: import_zod3.z.string().datetime(),
|
|
5399
|
+
// ISO 8601
|
|
5400
|
+
updatedFrom: import_zod3.z.string(),
|
|
5401
|
+
// commit hash
|
|
5402
|
+
metrics: import_zod3.z.record(ArchMetricCategorySchema, CategoryBaselineSchema)
|
|
5403
|
+
});
|
|
5404
|
+
var CategoryRegressionSchema = import_zod3.z.object({
|
|
5405
|
+
category: ArchMetricCategorySchema,
|
|
5406
|
+
baselineValue: import_zod3.z.number(),
|
|
5407
|
+
currentValue: import_zod3.z.number(),
|
|
5408
|
+
delta: import_zod3.z.number()
|
|
5409
|
+
});
|
|
5410
|
+
var ArchDiffResultSchema = import_zod3.z.object({
|
|
5411
|
+
passed: import_zod3.z.boolean(),
|
|
5412
|
+
newViolations: import_zod3.z.array(ViolationSchema),
|
|
5413
|
+
// in current but not in baseline -> FAIL
|
|
5414
|
+
resolvedViolations: import_zod3.z.array(import_zod3.z.string()),
|
|
5415
|
+
// in baseline but not in current -> celebrate
|
|
5416
|
+
preExisting: import_zod3.z.array(import_zod3.z.string()),
|
|
5417
|
+
// in both -> allowed, tracked
|
|
5418
|
+
regressions: import_zod3.z.array(CategoryRegressionSchema)
|
|
5419
|
+
// aggregate value exceeded baseline
|
|
5420
|
+
});
|
|
5421
|
+
var ThresholdConfigSchema = import_zod3.z.record(
|
|
5422
|
+
ArchMetricCategorySchema,
|
|
5423
|
+
import_zod3.z.union([import_zod3.z.number(), import_zod3.z.record(import_zod3.z.string(), import_zod3.z.number())])
|
|
5424
|
+
);
|
|
5425
|
+
var ArchConfigSchema = import_zod3.z.object({
|
|
5426
|
+
enabled: import_zod3.z.boolean().default(true),
|
|
5427
|
+
baselinePath: import_zod3.z.string().default(".harness/arch/baselines.json"),
|
|
5428
|
+
thresholds: ThresholdConfigSchema.default({}),
|
|
5429
|
+
modules: import_zod3.z.record(import_zod3.z.string(), ThresholdConfigSchema).default({})
|
|
5430
|
+
});
|
|
5431
|
+
var ConstraintRuleSchema = import_zod3.z.object({
|
|
5432
|
+
id: import_zod3.z.string(),
|
|
5433
|
+
// stable hash: sha256(category + ':' + scope + ':' + description)
|
|
5434
|
+
category: ArchMetricCategorySchema,
|
|
5435
|
+
description: import_zod3.z.string(),
|
|
5436
|
+
// e.g., "Layer 'services' must not import from 'ui'"
|
|
5437
|
+
scope: import_zod3.z.string(),
|
|
5438
|
+
// e.g., 'src/services/', 'project'
|
|
5439
|
+
targets: import_zod3.z.array(import_zod3.z.string()).optional()
|
|
5440
|
+
// forward-compat for governs edges
|
|
5441
|
+
});
|
|
5442
|
+
|
|
5443
|
+
// src/architecture/collectors/circular-deps.ts
|
|
5444
|
+
var import_node_path3 = require("path");
|
|
5445
|
+
|
|
5446
|
+
// src/architecture/collectors/hash.ts
|
|
5447
|
+
var import_node_crypto = require("crypto");
|
|
5448
|
+
function violationId(relativePath, category, normalizedDetail) {
|
|
5449
|
+
const path13 = relativePath.replace(/\\/g, "/");
|
|
5450
|
+
const input = `${path13}:${category}:${normalizedDetail}`;
|
|
5451
|
+
return (0, import_node_crypto.createHash)("sha256").update(input).digest("hex");
|
|
5452
|
+
}
|
|
5453
|
+
function constraintRuleId(category, scope, description) {
|
|
5454
|
+
const input = `${category}:${scope}:${description}`;
|
|
5455
|
+
return (0, import_node_crypto.createHash)("sha256").update(input).digest("hex");
|
|
5456
|
+
}
|
|
5457
|
+
|
|
5458
|
+
// src/architecture/collectors/circular-deps.ts
|
|
5459
|
+
var CircularDepsCollector = class {
|
|
5460
|
+
category = "circular-deps";
|
|
5461
|
+
getRules(_config, _rootDir) {
|
|
5462
|
+
const description = "No circular dependencies allowed";
|
|
5463
|
+
return [
|
|
5464
|
+
{
|
|
5465
|
+
id: constraintRuleId(this.category, "project", description),
|
|
5466
|
+
category: this.category,
|
|
5467
|
+
description,
|
|
5468
|
+
scope: "project"
|
|
5469
|
+
}
|
|
5470
|
+
];
|
|
5471
|
+
}
|
|
5472
|
+
async collect(_config, rootDir) {
|
|
5473
|
+
const files = await findFiles("**/*.ts", rootDir);
|
|
5474
|
+
const stubParser = {
|
|
5475
|
+
name: "typescript",
|
|
5476
|
+
extensions: [".ts", ".tsx"],
|
|
5477
|
+
parseFile: async () => ({ ok: false, error: { code: "PARSE_ERROR", message: "not needed" } }),
|
|
5478
|
+
extractImports: () => ({
|
|
5479
|
+
ok: false,
|
|
5480
|
+
error: { code: "EXTRACT_ERROR", message: "not needed" }
|
|
5481
|
+
}),
|
|
5482
|
+
extractExports: () => ({
|
|
5483
|
+
ok: false,
|
|
5484
|
+
error: { code: "EXTRACT_ERROR", message: "not needed" }
|
|
5485
|
+
}),
|
|
5486
|
+
health: async () => ({ ok: true, value: { available: true } })
|
|
5487
|
+
};
|
|
5488
|
+
const graphResult = await buildDependencyGraph(files, stubParser);
|
|
5489
|
+
if (!graphResult.ok) {
|
|
5490
|
+
return [
|
|
5491
|
+
{
|
|
5492
|
+
category: this.category,
|
|
5493
|
+
scope: "project",
|
|
5494
|
+
value: 0,
|
|
5495
|
+
violations: [],
|
|
5496
|
+
metadata: { error: "Failed to build dependency graph" }
|
|
5497
|
+
}
|
|
5498
|
+
];
|
|
5499
|
+
}
|
|
5500
|
+
const result = detectCircularDeps(graphResult.value);
|
|
5501
|
+
if (!result.ok) {
|
|
5502
|
+
return [
|
|
5503
|
+
{
|
|
5504
|
+
category: this.category,
|
|
5505
|
+
scope: "project",
|
|
5506
|
+
value: 0,
|
|
5507
|
+
violations: [],
|
|
5508
|
+
metadata: { error: "Failed to detect circular deps" }
|
|
5509
|
+
}
|
|
5510
|
+
];
|
|
5511
|
+
}
|
|
5512
|
+
const { cycles, largestCycle } = result.value;
|
|
5513
|
+
const violations = cycles.map((cycle) => {
|
|
5514
|
+
const cyclePath = cycle.cycle.map((f) => (0, import_node_path3.relative)(rootDir, f)).join(" -> ");
|
|
5515
|
+
const firstFile = (0, import_node_path3.relative)(rootDir, cycle.cycle[0]);
|
|
5516
|
+
return {
|
|
5517
|
+
id: violationId(firstFile, this.category, cyclePath),
|
|
5518
|
+
file: firstFile,
|
|
5519
|
+
detail: `Circular dependency: ${cyclePath}`,
|
|
5520
|
+
severity: cycle.severity
|
|
5521
|
+
};
|
|
5522
|
+
});
|
|
5523
|
+
return [
|
|
5524
|
+
{
|
|
5525
|
+
category: this.category,
|
|
5526
|
+
scope: "project",
|
|
5527
|
+
value: cycles.length,
|
|
5528
|
+
violations,
|
|
5529
|
+
metadata: { largestCycle, cycleCount: cycles.length }
|
|
5530
|
+
}
|
|
5531
|
+
];
|
|
5532
|
+
}
|
|
5533
|
+
};
|
|
5534
|
+
|
|
5535
|
+
// src/architecture/collectors/layer-violations.ts
|
|
5536
|
+
var import_node_path4 = require("path");
|
|
5537
|
+
var LayerViolationCollector = class {
|
|
5538
|
+
category = "layer-violations";
|
|
5539
|
+
getRules(_config, _rootDir) {
|
|
5540
|
+
const description = "No layer boundary violations allowed";
|
|
5541
|
+
return [
|
|
5542
|
+
{
|
|
5543
|
+
id: constraintRuleId(this.category, "project", description),
|
|
5544
|
+
category: this.category,
|
|
5545
|
+
description,
|
|
5546
|
+
scope: "project"
|
|
5547
|
+
}
|
|
5548
|
+
];
|
|
5549
|
+
}
|
|
5550
|
+
async collect(_config, rootDir) {
|
|
5551
|
+
const stubParser = {
|
|
5552
|
+
name: "typescript",
|
|
5553
|
+
extensions: [".ts", ".tsx"],
|
|
5554
|
+
parseFile: async () => ({ ok: false, error: { code: "PARSE_ERROR", message: "" } }),
|
|
5555
|
+
extractImports: () => ({ ok: false, error: { code: "EXTRACT_ERROR", message: "" } }),
|
|
5556
|
+
extractExports: () => ({ ok: false, error: { code: "EXTRACT_ERROR", message: "" } }),
|
|
5557
|
+
health: async () => ({ ok: true, value: { available: true } })
|
|
5558
|
+
};
|
|
5559
|
+
const result = await validateDependencies({
|
|
5560
|
+
layers: [],
|
|
5561
|
+
rootDir,
|
|
5562
|
+
parser: stubParser,
|
|
5563
|
+
fallbackBehavior: "skip"
|
|
5564
|
+
});
|
|
5565
|
+
if (!result.ok) {
|
|
5566
|
+
return [
|
|
5567
|
+
{
|
|
5568
|
+
category: this.category,
|
|
5569
|
+
scope: "project",
|
|
5570
|
+
value: 0,
|
|
5571
|
+
violations: [],
|
|
5572
|
+
metadata: { error: "Failed to validate dependencies" }
|
|
5573
|
+
}
|
|
5574
|
+
];
|
|
5575
|
+
}
|
|
5576
|
+
const layerViolations = result.value.violations.filter(
|
|
5577
|
+
(v) => v.reason === "WRONG_LAYER"
|
|
5578
|
+
);
|
|
5579
|
+
const violations = layerViolations.map((v) => {
|
|
5580
|
+
const relFile = (0, import_node_path4.relative)(rootDir, v.file);
|
|
5581
|
+
const relImport = (0, import_node_path4.relative)(rootDir, v.imports);
|
|
5582
|
+
const detail = `${v.fromLayer} -> ${v.toLayer}: ${relFile} imports ${relImport}`;
|
|
5583
|
+
return {
|
|
5584
|
+
id: violationId(relFile, this.category, detail),
|
|
5585
|
+
file: relFile,
|
|
5586
|
+
category: this.category,
|
|
5587
|
+
detail,
|
|
5588
|
+
severity: "error"
|
|
5589
|
+
};
|
|
5590
|
+
});
|
|
5591
|
+
return [
|
|
5592
|
+
{
|
|
5593
|
+
category: this.category,
|
|
5594
|
+
scope: "project",
|
|
5595
|
+
value: violations.length,
|
|
5596
|
+
violations
|
|
5597
|
+
}
|
|
5598
|
+
];
|
|
5599
|
+
}
|
|
5600
|
+
};
|
|
5601
|
+
|
|
5602
|
+
// src/architecture/collectors/complexity.ts
|
|
5603
|
+
var import_node_path5 = require("path");
|
|
5604
|
+
var ComplexityCollector = class {
|
|
5605
|
+
category = "complexity";
|
|
5606
|
+
getRules(_config, _rootDir) {
|
|
5607
|
+
const description = "Cyclomatic complexity must stay within thresholds";
|
|
5608
|
+
return [
|
|
5609
|
+
{
|
|
5610
|
+
id: constraintRuleId(this.category, "project", description),
|
|
5611
|
+
category: this.category,
|
|
5612
|
+
description,
|
|
5613
|
+
scope: "project"
|
|
5614
|
+
}
|
|
5615
|
+
];
|
|
5616
|
+
}
|
|
5617
|
+
async collect(_config, rootDir) {
|
|
5618
|
+
const files = await findFiles("**/*.ts", rootDir);
|
|
5619
|
+
const snapshot = {
|
|
5620
|
+
files: files.map((f) => ({
|
|
5621
|
+
path: f,
|
|
5622
|
+
ast: { type: "Program", body: null, language: "typescript" },
|
|
5623
|
+
imports: [],
|
|
5624
|
+
exports: [],
|
|
5625
|
+
internalSymbols: [],
|
|
5626
|
+
jsDocComments: []
|
|
5627
|
+
})),
|
|
5628
|
+
dependencyGraph: { nodes: [], edges: [] },
|
|
5629
|
+
exportMap: { byFile: /* @__PURE__ */ new Map(), byName: /* @__PURE__ */ new Map() },
|
|
5630
|
+
docs: [],
|
|
5631
|
+
codeReferences: [],
|
|
5632
|
+
entryPoints: [],
|
|
5633
|
+
rootDir,
|
|
5634
|
+
config: { rootDir, analyze: {} },
|
|
5635
|
+
buildTime: 0
|
|
5636
|
+
};
|
|
5637
|
+
const complexityThreshold = _config.thresholds.complexity;
|
|
5638
|
+
const maxComplexity = typeof complexityThreshold === "number" ? complexityThreshold : complexityThreshold?.max ?? 15;
|
|
5639
|
+
const complexityConfig = {
|
|
5640
|
+
thresholds: {
|
|
5641
|
+
cyclomaticComplexity: {
|
|
5642
|
+
error: maxComplexity,
|
|
5643
|
+
warn: Math.floor(maxComplexity * 0.7)
|
|
5644
|
+
}
|
|
5645
|
+
}
|
|
5646
|
+
};
|
|
5647
|
+
const result = await detectComplexityViolations(snapshot, complexityConfig);
|
|
5648
|
+
if (!result.ok) {
|
|
5649
|
+
return [
|
|
5650
|
+
{
|
|
5651
|
+
category: this.category,
|
|
5652
|
+
scope: "project",
|
|
5653
|
+
value: 0,
|
|
5654
|
+
violations: [],
|
|
5655
|
+
metadata: { error: "Failed to detect complexity violations" }
|
|
5656
|
+
}
|
|
5657
|
+
];
|
|
5658
|
+
}
|
|
5659
|
+
const { violations: complexityViolations, stats } = result.value;
|
|
5660
|
+
const filtered = complexityViolations.filter(
|
|
5661
|
+
(v) => v.severity === "error" || v.severity === "warning"
|
|
5662
|
+
);
|
|
5663
|
+
const violations = filtered.map((v) => {
|
|
5664
|
+
const relFile = (0, import_node_path5.relative)(rootDir, v.file);
|
|
5665
|
+
const idDetail = `${v.metric}:${v.function}`;
|
|
5666
|
+
return {
|
|
5667
|
+
id: violationId(relFile, this.category, idDetail),
|
|
5668
|
+
file: relFile,
|
|
5669
|
+
category: this.category,
|
|
5670
|
+
detail: `${v.metric}=${v.value} in ${v.function} (threshold: ${v.threshold})`,
|
|
5671
|
+
severity: v.severity
|
|
5672
|
+
};
|
|
5673
|
+
});
|
|
5674
|
+
return [
|
|
5675
|
+
{
|
|
5676
|
+
category: this.category,
|
|
5677
|
+
scope: "project",
|
|
5678
|
+
value: violations.length,
|
|
5679
|
+
violations,
|
|
5680
|
+
metadata: {
|
|
5681
|
+
filesAnalyzed: stats.filesAnalyzed,
|
|
5682
|
+
functionsAnalyzed: stats.functionsAnalyzed
|
|
5683
|
+
}
|
|
5684
|
+
}
|
|
5685
|
+
];
|
|
5686
|
+
}
|
|
5687
|
+
};
|
|
5688
|
+
|
|
5689
|
+
// src/architecture/collectors/coupling.ts
|
|
5690
|
+
var import_node_path6 = require("path");
|
|
5691
|
+
var CouplingCollector = class {
|
|
5692
|
+
category = "coupling";
|
|
5693
|
+
getRules(_config, _rootDir) {
|
|
5694
|
+
const description = "Coupling metrics must stay within thresholds";
|
|
5695
|
+
return [
|
|
5696
|
+
{
|
|
5697
|
+
id: constraintRuleId(this.category, "project", description),
|
|
5698
|
+
category: this.category,
|
|
5699
|
+
description,
|
|
5700
|
+
scope: "project"
|
|
5701
|
+
}
|
|
5702
|
+
];
|
|
5703
|
+
}
|
|
5704
|
+
async collect(_config, rootDir) {
|
|
5705
|
+
const files = await findFiles("**/*.ts", rootDir);
|
|
5706
|
+
const snapshot = {
|
|
5707
|
+
files: files.map((f) => ({
|
|
5708
|
+
path: f,
|
|
5709
|
+
ast: { type: "Program", body: null, language: "typescript" },
|
|
5710
|
+
imports: [],
|
|
5711
|
+
exports: [],
|
|
5712
|
+
internalSymbols: [],
|
|
5713
|
+
jsDocComments: []
|
|
5714
|
+
})),
|
|
5715
|
+
dependencyGraph: { nodes: [], edges: [] },
|
|
5716
|
+
exportMap: { byFile: /* @__PURE__ */ new Map(), byName: /* @__PURE__ */ new Map() },
|
|
5717
|
+
docs: [],
|
|
5718
|
+
codeReferences: [],
|
|
5719
|
+
entryPoints: [],
|
|
5720
|
+
rootDir,
|
|
5721
|
+
config: { rootDir, analyze: {} },
|
|
5722
|
+
buildTime: 0
|
|
5723
|
+
};
|
|
5724
|
+
const result = await detectCouplingViolations(snapshot);
|
|
5725
|
+
if (!result.ok) {
|
|
5726
|
+
return [
|
|
5727
|
+
{
|
|
5728
|
+
category: this.category,
|
|
5729
|
+
scope: "project",
|
|
5730
|
+
value: 0,
|
|
5731
|
+
violations: [],
|
|
5732
|
+
metadata: { error: "Failed to detect coupling violations" }
|
|
5733
|
+
}
|
|
5734
|
+
];
|
|
5735
|
+
}
|
|
5736
|
+
const { violations: couplingViolations, stats } = result.value;
|
|
5737
|
+
const filtered = couplingViolations.filter(
|
|
5738
|
+
(v) => v.severity === "error" || v.severity === "warning"
|
|
5739
|
+
);
|
|
5740
|
+
const violations = filtered.map((v) => {
|
|
5741
|
+
const relFile = (0, import_node_path6.relative)(rootDir, v.file);
|
|
5742
|
+
const idDetail = `${v.metric}`;
|
|
5743
|
+
return {
|
|
5744
|
+
id: violationId(relFile, this.category, idDetail),
|
|
5745
|
+
file: relFile,
|
|
5746
|
+
category: this.category,
|
|
5747
|
+
detail: `${v.metric}=${v.value} (threshold: ${v.threshold})`,
|
|
5748
|
+
severity: v.severity
|
|
5749
|
+
};
|
|
5750
|
+
});
|
|
5751
|
+
return [
|
|
5752
|
+
{
|
|
5753
|
+
category: this.category,
|
|
5754
|
+
scope: "project",
|
|
5755
|
+
value: violations.length,
|
|
5756
|
+
violations,
|
|
5757
|
+
metadata: { filesAnalyzed: stats.filesAnalyzed }
|
|
5758
|
+
}
|
|
5759
|
+
];
|
|
5760
|
+
}
|
|
5761
|
+
};
|
|
5762
|
+
|
|
5763
|
+
// src/architecture/collectors/forbidden-imports.ts
|
|
5764
|
+
var import_node_path7 = require("path");
|
|
5765
|
+
var ForbiddenImportCollector = class {
|
|
5766
|
+
category = "forbidden-imports";
|
|
5767
|
+
getRules(_config, _rootDir) {
|
|
5768
|
+
const description = "No forbidden imports allowed";
|
|
5769
|
+
return [
|
|
5770
|
+
{
|
|
5771
|
+
id: constraintRuleId(this.category, "project", description),
|
|
5772
|
+
category: this.category,
|
|
5773
|
+
description,
|
|
5774
|
+
scope: "project"
|
|
5775
|
+
}
|
|
5776
|
+
];
|
|
5777
|
+
}
|
|
5778
|
+
async collect(_config, rootDir) {
|
|
5779
|
+
const stubParser = {
|
|
5780
|
+
name: "typescript",
|
|
5781
|
+
extensions: [".ts", ".tsx"],
|
|
5782
|
+
parseFile: async () => ({ ok: false, error: { code: "PARSE_ERROR", message: "" } }),
|
|
5783
|
+
extractImports: () => ({ ok: false, error: { code: "EXTRACT_ERROR", message: "" } }),
|
|
5784
|
+
extractExports: () => ({ ok: false, error: { code: "EXTRACT_ERROR", message: "" } }),
|
|
5785
|
+
health: async () => ({ ok: true, value: { available: true } })
|
|
5786
|
+
};
|
|
5787
|
+
const result = await validateDependencies({
|
|
5788
|
+
layers: [],
|
|
5789
|
+
rootDir,
|
|
5790
|
+
parser: stubParser,
|
|
5791
|
+
fallbackBehavior: "skip"
|
|
5792
|
+
});
|
|
5793
|
+
if (!result.ok) {
|
|
5794
|
+
return [
|
|
5795
|
+
{
|
|
5796
|
+
category: this.category,
|
|
5797
|
+
scope: "project",
|
|
5798
|
+
value: 0,
|
|
5799
|
+
violations: [],
|
|
5800
|
+
metadata: { error: "Failed to validate dependencies" }
|
|
5801
|
+
}
|
|
5802
|
+
];
|
|
5803
|
+
}
|
|
5804
|
+
const forbidden = result.value.violations.filter(
|
|
5805
|
+
(v) => v.reason === "FORBIDDEN_IMPORT"
|
|
5806
|
+
);
|
|
5807
|
+
const violations = forbidden.map((v) => {
|
|
5808
|
+
const relFile = (0, import_node_path7.relative)(rootDir, v.file);
|
|
5809
|
+
const relImport = (0, import_node_path7.relative)(rootDir, v.imports);
|
|
5810
|
+
const detail = `forbidden import: ${relFile} -> ${relImport}`;
|
|
5811
|
+
return {
|
|
5812
|
+
id: violationId(relFile, this.category, detail),
|
|
5813
|
+
file: relFile,
|
|
5814
|
+
category: this.category,
|
|
5815
|
+
detail,
|
|
5816
|
+
severity: "error"
|
|
5817
|
+
};
|
|
5818
|
+
});
|
|
5819
|
+
return [
|
|
5820
|
+
{
|
|
5821
|
+
category: this.category,
|
|
5822
|
+
scope: "project",
|
|
5823
|
+
value: violations.length,
|
|
5824
|
+
violations
|
|
5825
|
+
}
|
|
5826
|
+
];
|
|
5827
|
+
}
|
|
5828
|
+
};
|
|
5829
|
+
|
|
5830
|
+
// src/architecture/collectors/module-size.ts
|
|
5831
|
+
var import_promises2 = require("fs/promises");
|
|
5832
|
+
var import_node_path8 = require("path");
|
|
5833
|
+
async function discoverModules(rootDir) {
|
|
5834
|
+
const modules = [];
|
|
5835
|
+
async function scanDir(dir) {
|
|
5836
|
+
let entries;
|
|
5837
|
+
try {
|
|
5838
|
+
entries = await (0, import_promises2.readdir)(dir, { withFileTypes: true });
|
|
5839
|
+
} catch {
|
|
5840
|
+
return;
|
|
5841
|
+
}
|
|
5842
|
+
const tsFiles = [];
|
|
5843
|
+
const subdirs = [];
|
|
5844
|
+
for (const entry of entries) {
|
|
5845
|
+
if (entry.name.startsWith(".") || entry.name === "node_modules" || entry.name === "dist") {
|
|
5846
|
+
continue;
|
|
5847
|
+
}
|
|
5848
|
+
const fullPath = (0, import_node_path8.join)(dir, entry.name);
|
|
5849
|
+
if (entry.isDirectory()) {
|
|
5850
|
+
subdirs.push(fullPath);
|
|
5851
|
+
} else if (entry.isFile() && (entry.name.endsWith(".ts") || entry.name.endsWith(".tsx")) && !entry.name.endsWith(".test.ts") && !entry.name.endsWith(".test.tsx") && !entry.name.endsWith(".spec.ts")) {
|
|
5852
|
+
tsFiles.push(fullPath);
|
|
5853
|
+
}
|
|
5854
|
+
}
|
|
5855
|
+
if (tsFiles.length > 0) {
|
|
5856
|
+
let totalLoc = 0;
|
|
5857
|
+
for (const f of tsFiles) {
|
|
5858
|
+
try {
|
|
5859
|
+
const content = await (0, import_promises2.readFile)(f, "utf-8");
|
|
5860
|
+
totalLoc += content.split("\n").filter((line) => line.trim().length > 0).length;
|
|
5861
|
+
} catch {
|
|
5862
|
+
}
|
|
5863
|
+
}
|
|
5864
|
+
modules.push({
|
|
5865
|
+
modulePath: (0, import_node_path8.relative)(rootDir, dir),
|
|
5866
|
+
fileCount: tsFiles.length,
|
|
5867
|
+
totalLoc,
|
|
5868
|
+
files: tsFiles.map((f) => (0, import_node_path8.relative)(rootDir, f))
|
|
5869
|
+
});
|
|
5870
|
+
}
|
|
5871
|
+
for (const sub of subdirs) {
|
|
5872
|
+
await scanDir(sub);
|
|
5873
|
+
}
|
|
5874
|
+
}
|
|
5875
|
+
await scanDir(rootDir);
|
|
5876
|
+
return modules;
|
|
5877
|
+
}
|
|
5878
|
+
var ModuleSizeCollector = class {
|
|
5879
|
+
category = "module-size";
|
|
5880
|
+
getRules(config, _rootDir) {
|
|
5881
|
+
const thresholds = config.thresholds["module-size"];
|
|
5882
|
+
let maxLoc = Infinity;
|
|
5883
|
+
let maxFiles = Infinity;
|
|
5884
|
+
if (typeof thresholds === "object" && thresholds !== null) {
|
|
5885
|
+
const t = thresholds;
|
|
5886
|
+
if (t.maxLoc !== void 0) maxLoc = t.maxLoc;
|
|
5887
|
+
if (t.maxFiles !== void 0) maxFiles = t.maxFiles;
|
|
5888
|
+
}
|
|
5889
|
+
const rules = [];
|
|
5890
|
+
if (maxLoc < Infinity) {
|
|
5891
|
+
const desc = `Module LOC must not exceed ${maxLoc}`;
|
|
5892
|
+
rules.push({
|
|
5893
|
+
id: constraintRuleId(this.category, "project", desc),
|
|
5894
|
+
category: this.category,
|
|
5895
|
+
description: desc,
|
|
5896
|
+
scope: "project"
|
|
5897
|
+
});
|
|
5898
|
+
}
|
|
5899
|
+
if (maxFiles < Infinity) {
|
|
5900
|
+
const desc = `Module file count must not exceed ${maxFiles}`;
|
|
5901
|
+
rules.push({
|
|
5902
|
+
id: constraintRuleId(this.category, "project", desc),
|
|
5903
|
+
category: this.category,
|
|
5904
|
+
description: desc,
|
|
5905
|
+
scope: "project"
|
|
5906
|
+
});
|
|
5907
|
+
}
|
|
5908
|
+
if (rules.length === 0) {
|
|
5909
|
+
const desc = "Module size must stay within thresholds";
|
|
5910
|
+
rules.push({
|
|
5911
|
+
id: constraintRuleId(this.category, "project", desc),
|
|
5912
|
+
category: this.category,
|
|
5913
|
+
description: desc,
|
|
5914
|
+
scope: "project"
|
|
5915
|
+
});
|
|
5916
|
+
}
|
|
5917
|
+
return rules;
|
|
5918
|
+
}
|
|
5919
|
+
async collect(config, rootDir) {
|
|
5920
|
+
const modules = await discoverModules(rootDir);
|
|
5921
|
+
const thresholds = config.thresholds["module-size"];
|
|
5922
|
+
let maxLoc = Infinity;
|
|
5923
|
+
let maxFiles = Infinity;
|
|
5924
|
+
if (typeof thresholds === "object" && thresholds !== null) {
|
|
5925
|
+
const t = thresholds;
|
|
5926
|
+
if (t.maxLoc !== void 0) maxLoc = t.maxLoc;
|
|
5927
|
+
if (t.maxFiles !== void 0) maxFiles = t.maxFiles;
|
|
5928
|
+
}
|
|
5929
|
+
return modules.map((mod) => {
|
|
5930
|
+
const violations = [];
|
|
5931
|
+
if (mod.totalLoc > maxLoc) {
|
|
5932
|
+
violations.push({
|
|
5933
|
+
id: violationId(mod.modulePath, this.category, "totalLoc-exceeded"),
|
|
5934
|
+
file: mod.modulePath,
|
|
5935
|
+
detail: `Module has ${mod.totalLoc} lines of code (threshold: ${maxLoc})`,
|
|
5936
|
+
severity: "warning"
|
|
5937
|
+
});
|
|
5938
|
+
}
|
|
5939
|
+
if (mod.fileCount > maxFiles) {
|
|
5940
|
+
violations.push({
|
|
5941
|
+
id: violationId(mod.modulePath, this.category, "fileCount-exceeded"),
|
|
5942
|
+
file: mod.modulePath,
|
|
5943
|
+
detail: `Module has ${mod.fileCount} files (threshold: ${maxFiles})`,
|
|
5944
|
+
severity: "warning"
|
|
5945
|
+
});
|
|
5946
|
+
}
|
|
5947
|
+
return {
|
|
5948
|
+
category: this.category,
|
|
5949
|
+
scope: mod.modulePath,
|
|
5950
|
+
value: mod.totalLoc,
|
|
5951
|
+
violations,
|
|
5952
|
+
metadata: { fileCount: mod.fileCount, totalLoc: mod.totalLoc }
|
|
5953
|
+
};
|
|
5954
|
+
});
|
|
5955
|
+
}
|
|
5956
|
+
};
|
|
5957
|
+
|
|
5958
|
+
// src/architecture/collectors/dep-depth.ts
|
|
5959
|
+
var import_promises3 = require("fs/promises");
|
|
5960
|
+
var import_node_path9 = require("path");
|
|
5961
|
+
function extractImportSources(content, filePath) {
|
|
5962
|
+
const importRegex = /(?:import|export)\s+.*?from\s+['"](\.[^'"]+)['"]/g;
|
|
5963
|
+
const dynamicRegex = /import\s*\(\s*['"](\.[^'"]+)['"]\s*\)/g;
|
|
5964
|
+
const sources = [];
|
|
5965
|
+
const dir = (0, import_node_path9.dirname)(filePath);
|
|
5966
|
+
for (const regex of [importRegex, dynamicRegex]) {
|
|
5967
|
+
let match;
|
|
5968
|
+
while ((match = regex.exec(content)) !== null) {
|
|
5969
|
+
let resolved = (0, import_node_path9.resolve)(dir, match[1]);
|
|
5970
|
+
if (!resolved.endsWith(".ts") && !resolved.endsWith(".tsx")) {
|
|
5971
|
+
resolved += ".ts";
|
|
5972
|
+
}
|
|
5973
|
+
sources.push(resolved);
|
|
5974
|
+
}
|
|
5975
|
+
}
|
|
5976
|
+
return sources;
|
|
5977
|
+
}
|
|
5978
|
+
async function collectTsFiles(dir) {
|
|
5979
|
+
const results = [];
|
|
5980
|
+
async function scan(d) {
|
|
5981
|
+
let entries;
|
|
5982
|
+
try {
|
|
5983
|
+
entries = await (0, import_promises3.readdir)(d, { withFileTypes: true });
|
|
5984
|
+
} catch {
|
|
5985
|
+
return;
|
|
5986
|
+
}
|
|
5987
|
+
for (const entry of entries) {
|
|
5988
|
+
if (entry.name.startsWith(".") || entry.name === "node_modules" || entry.name === "dist")
|
|
5989
|
+
continue;
|
|
5990
|
+
const fullPath = (0, import_node_path9.join)(d, entry.name);
|
|
5991
|
+
if (entry.isDirectory()) {
|
|
5992
|
+
await scan(fullPath);
|
|
5993
|
+
} else if (entry.isFile() && (entry.name.endsWith(".ts") || entry.name.endsWith(".tsx")) && !entry.name.endsWith(".test.ts") && !entry.name.endsWith(".test.tsx") && !entry.name.endsWith(".spec.ts")) {
|
|
5994
|
+
results.push(fullPath);
|
|
5995
|
+
}
|
|
5996
|
+
}
|
|
5997
|
+
}
|
|
5998
|
+
await scan(dir);
|
|
5999
|
+
return results;
|
|
6000
|
+
}
|
|
6001
|
+
function computeLongestChain(file, graph, visited, memo) {
|
|
6002
|
+
if (memo.has(file)) return memo.get(file);
|
|
6003
|
+
if (visited.has(file)) return 0;
|
|
6004
|
+
visited.add(file);
|
|
6005
|
+
const deps = graph.get(file) || [];
|
|
6006
|
+
let maxDepth = 0;
|
|
6007
|
+
for (const dep of deps) {
|
|
6008
|
+
const depth = 1 + computeLongestChain(dep, graph, visited, memo);
|
|
6009
|
+
if (depth > maxDepth) maxDepth = depth;
|
|
6010
|
+
}
|
|
6011
|
+
visited.delete(file);
|
|
6012
|
+
memo.set(file, maxDepth);
|
|
6013
|
+
return maxDepth;
|
|
6014
|
+
}
|
|
6015
|
+
var DepDepthCollector = class {
|
|
6016
|
+
category = "dependency-depth";
|
|
6017
|
+
getRules(config, _rootDir) {
|
|
6018
|
+
const threshold = typeof config.thresholds["dependency-depth"] === "number" ? config.thresholds["dependency-depth"] : null;
|
|
6019
|
+
const desc = threshold !== null ? `Dependency chain depth must not exceed ${threshold}` : "Dependency chain depth must stay within thresholds";
|
|
6020
|
+
return [
|
|
6021
|
+
{
|
|
6022
|
+
id: constraintRuleId(this.category, "project", desc),
|
|
6023
|
+
category: this.category,
|
|
6024
|
+
description: desc,
|
|
6025
|
+
scope: "project"
|
|
6026
|
+
}
|
|
6027
|
+
];
|
|
6028
|
+
}
|
|
6029
|
+
async collect(config, rootDir) {
|
|
6030
|
+
const allFiles = await collectTsFiles(rootDir);
|
|
6031
|
+
const graph = /* @__PURE__ */ new Map();
|
|
6032
|
+
const fileSet = new Set(allFiles);
|
|
6033
|
+
for (const file of allFiles) {
|
|
6034
|
+
try {
|
|
6035
|
+
const content = await (0, import_promises3.readFile)(file, "utf-8");
|
|
6036
|
+
const imports = extractImportSources(content, file).filter((imp) => fileSet.has(imp));
|
|
6037
|
+
graph.set(file, imports);
|
|
6038
|
+
} catch {
|
|
6039
|
+
graph.set(file, []);
|
|
6040
|
+
}
|
|
6041
|
+
}
|
|
6042
|
+
const moduleMap = /* @__PURE__ */ new Map();
|
|
6043
|
+
for (const file of allFiles) {
|
|
6044
|
+
const relDir = (0, import_node_path9.relative)(rootDir, (0, import_node_path9.dirname)(file));
|
|
6045
|
+
if (!moduleMap.has(relDir)) moduleMap.set(relDir, []);
|
|
6046
|
+
moduleMap.get(relDir).push(file);
|
|
6047
|
+
}
|
|
6048
|
+
const memo = /* @__PURE__ */ new Map();
|
|
6049
|
+
const threshold = typeof config.thresholds["dependency-depth"] === "number" ? config.thresholds["dependency-depth"] : Infinity;
|
|
6050
|
+
const results = [];
|
|
6051
|
+
for (const [modulePath, files] of moduleMap) {
|
|
6052
|
+
let longestChain = 0;
|
|
6053
|
+
for (const file of files) {
|
|
6054
|
+
const depth = computeLongestChain(file, graph, /* @__PURE__ */ new Set(), memo);
|
|
6055
|
+
if (depth > longestChain) longestChain = depth;
|
|
6056
|
+
}
|
|
6057
|
+
const violations = [];
|
|
6058
|
+
if (longestChain > threshold) {
|
|
6059
|
+
violations.push({
|
|
6060
|
+
id: violationId(modulePath, this.category, "depth-exceeded"),
|
|
6061
|
+
file: modulePath,
|
|
6062
|
+
detail: `Import chain depth is ${longestChain} (threshold: ${threshold})`,
|
|
6063
|
+
severity: "warning"
|
|
6064
|
+
});
|
|
6065
|
+
}
|
|
6066
|
+
results.push({
|
|
6067
|
+
category: this.category,
|
|
6068
|
+
scope: modulePath,
|
|
6069
|
+
value: longestChain,
|
|
6070
|
+
violations,
|
|
6071
|
+
metadata: { longestChain }
|
|
6072
|
+
});
|
|
6073
|
+
}
|
|
6074
|
+
return results;
|
|
6075
|
+
}
|
|
6076
|
+
};
|
|
6077
|
+
|
|
6078
|
+
// src/architecture/collectors/index.ts
|
|
6079
|
+
var defaultCollectors = [
|
|
6080
|
+
new CircularDepsCollector(),
|
|
6081
|
+
new LayerViolationCollector(),
|
|
6082
|
+
new ComplexityCollector(),
|
|
6083
|
+
new CouplingCollector(),
|
|
6084
|
+
new ForbiddenImportCollector(),
|
|
6085
|
+
new ModuleSizeCollector(),
|
|
6086
|
+
new DepDepthCollector()
|
|
6087
|
+
];
|
|
6088
|
+
async function runAll(config, rootDir, collectors = defaultCollectors) {
|
|
6089
|
+
const results = await Promise.allSettled(collectors.map((c) => c.collect(config, rootDir)));
|
|
6090
|
+
const allResults = [];
|
|
6091
|
+
for (let i = 0; i < results.length; i++) {
|
|
6092
|
+
const result = results[i];
|
|
6093
|
+
if (result.status === "fulfilled") {
|
|
6094
|
+
allResults.push(...result.value);
|
|
6095
|
+
} else {
|
|
6096
|
+
allResults.push({
|
|
6097
|
+
category: collectors[i].category,
|
|
6098
|
+
scope: "project",
|
|
6099
|
+
value: 0,
|
|
6100
|
+
violations: [],
|
|
6101
|
+
metadata: { error: String(result.reason) }
|
|
6102
|
+
});
|
|
6103
|
+
}
|
|
6104
|
+
}
|
|
6105
|
+
return allResults;
|
|
6106
|
+
}
|
|
6107
|
+
|
|
6108
|
+
// src/architecture/sync-constraints.ts
|
|
6109
|
+
function syncConstraintNodes(store, rules, violations) {
|
|
6110
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
6111
|
+
const ruleIds = new Set(rules.map((r) => r.id));
|
|
6112
|
+
const violationsByCategory = /* @__PURE__ */ new Map();
|
|
6113
|
+
for (const result of violations) {
|
|
6114
|
+
const files = result.violations.map((v) => v.file);
|
|
6115
|
+
const existing = violationsByCategory.get(result.category) ?? [];
|
|
6116
|
+
violationsByCategory.set(result.category, [...existing, ...files]);
|
|
6117
|
+
}
|
|
6118
|
+
const existingNodesById = /* @__PURE__ */ new Map();
|
|
6119
|
+
for (const node of store.findNodes({ type: "constraint" })) {
|
|
6120
|
+
existingNodesById.set(node.id, node);
|
|
6121
|
+
}
|
|
6122
|
+
for (const rule of rules) {
|
|
6123
|
+
const existing = existingNodesById.get(rule.id);
|
|
6124
|
+
const createdAt = existing?.createdAt ?? now;
|
|
6125
|
+
const previousLastViolatedAt = existing?.lastViolatedAt ?? null;
|
|
6126
|
+
const hasViolation = hasMatchingViolation(rule, violationsByCategory);
|
|
6127
|
+
const lastViolatedAt = hasViolation ? now : previousLastViolatedAt;
|
|
6128
|
+
store.upsertNode({
|
|
6129
|
+
id: rule.id,
|
|
6130
|
+
type: "constraint",
|
|
6131
|
+
name: rule.description,
|
|
6132
|
+
category: rule.category,
|
|
6133
|
+
scope: rule.scope,
|
|
6134
|
+
createdAt,
|
|
6135
|
+
lastViolatedAt
|
|
6136
|
+
});
|
|
6137
|
+
}
|
|
6138
|
+
const existingConstraints = store.findNodes({ type: "constraint" });
|
|
6139
|
+
for (const node of existingConstraints) {
|
|
6140
|
+
if (!ruleIds.has(node.id)) {
|
|
6141
|
+
store.removeNode(node.id);
|
|
6142
|
+
}
|
|
6143
|
+
}
|
|
6144
|
+
}
|
|
6145
|
+
function hasMatchingViolation(rule, violationsByCategory) {
|
|
6146
|
+
const files = violationsByCategory.get(rule.category);
|
|
6147
|
+
if (!files || files.length === 0) return false;
|
|
6148
|
+
if (rule.scope === "project") return true;
|
|
6149
|
+
return files.some((file) => file.startsWith(rule.scope));
|
|
6150
|
+
}
|
|
6151
|
+
|
|
6152
|
+
// src/architecture/detect-stale.ts
|
|
6153
|
+
function detectStaleConstraints(store, windowDays = 30, category) {
|
|
6154
|
+
const now = Date.now();
|
|
6155
|
+
const windowMs = windowDays * 24 * 60 * 60 * 1e3;
|
|
6156
|
+
const cutoff = now - windowMs;
|
|
6157
|
+
let constraints = store.findNodes({ type: "constraint" });
|
|
6158
|
+
if (category) {
|
|
6159
|
+
constraints = constraints.filter((n) => n.category === category);
|
|
6160
|
+
}
|
|
6161
|
+
const totalConstraints = constraints.length;
|
|
6162
|
+
const staleConstraints = [];
|
|
6163
|
+
for (const node of constraints) {
|
|
6164
|
+
const lastViolatedAt = node.lastViolatedAt ?? null;
|
|
6165
|
+
const createdAt = node.createdAt;
|
|
6166
|
+
const comparisonTimestamp = lastViolatedAt ?? createdAt;
|
|
6167
|
+
if (!comparisonTimestamp) continue;
|
|
6168
|
+
const timestampMs = new Date(comparisonTimestamp).getTime();
|
|
6169
|
+
if (timestampMs < cutoff) {
|
|
6170
|
+
const daysSince = Math.floor((now - timestampMs) / (24 * 60 * 60 * 1e3));
|
|
6171
|
+
staleConstraints.push({
|
|
6172
|
+
id: node.id,
|
|
6173
|
+
category: node.category,
|
|
6174
|
+
description: node.name ?? "",
|
|
6175
|
+
scope: node.scope ?? "project",
|
|
6176
|
+
lastViolatedAt,
|
|
6177
|
+
daysSinceLastViolation: daysSince
|
|
6178
|
+
});
|
|
6179
|
+
}
|
|
6180
|
+
}
|
|
6181
|
+
staleConstraints.sort((a, b) => b.daysSinceLastViolation - a.daysSinceLastViolation);
|
|
6182
|
+
return { staleConstraints, totalConstraints, windowDays };
|
|
6183
|
+
}
|
|
6184
|
+
|
|
6185
|
+
// src/architecture/baseline-manager.ts
|
|
6186
|
+
var import_node_fs3 = require("fs");
|
|
6187
|
+
var import_node_crypto2 = require("crypto");
|
|
6188
|
+
var import_node_path10 = require("path");
|
|
6189
|
+
var ArchBaselineManager = class {
|
|
6190
|
+
baselinesPath;
|
|
6191
|
+
constructor(projectRoot, baselinePath) {
|
|
6192
|
+
this.baselinesPath = baselinePath ? (0, import_node_path10.join)(projectRoot, baselinePath) : (0, import_node_path10.join)(projectRoot, ".harness", "arch", "baselines.json");
|
|
6193
|
+
}
|
|
6194
|
+
/**
|
|
6195
|
+
* Snapshot the current metric results into an ArchBaseline.
|
|
6196
|
+
* Aggregates multiple MetricResults for the same category by summing values
|
|
6197
|
+
* and concatenating violation IDs.
|
|
6198
|
+
*/
|
|
6199
|
+
capture(results, commitHash) {
|
|
6200
|
+
const metrics = {};
|
|
6201
|
+
for (const result of results) {
|
|
6202
|
+
const existing = metrics[result.category];
|
|
6203
|
+
if (existing) {
|
|
6204
|
+
existing.value += result.value;
|
|
6205
|
+
existing.violationIds.push(...result.violations.map((v) => v.id));
|
|
6206
|
+
} else {
|
|
6207
|
+
metrics[result.category] = {
|
|
6208
|
+
value: result.value,
|
|
6209
|
+
violationIds: result.violations.map((v) => v.id)
|
|
6210
|
+
};
|
|
6211
|
+
}
|
|
6212
|
+
}
|
|
6213
|
+
return {
|
|
6214
|
+
version: 1,
|
|
6215
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
6216
|
+
updatedFrom: commitHash,
|
|
6217
|
+
metrics
|
|
6218
|
+
};
|
|
6219
|
+
}
|
|
6220
|
+
/**
|
|
6221
|
+
* Load the baselines file from disk.
|
|
6222
|
+
* Returns null if the file does not exist, contains invalid JSON,
|
|
6223
|
+
* or fails ArchBaselineSchema validation.
|
|
6224
|
+
*/
|
|
6225
|
+
load() {
|
|
6226
|
+
if (!(0, import_node_fs3.existsSync)(this.baselinesPath)) {
|
|
6227
|
+
console.error(`Baseline file not found at: ${this.baselinesPath}`);
|
|
6228
|
+
return null;
|
|
6229
|
+
}
|
|
6230
|
+
try {
|
|
6231
|
+
const raw = (0, import_node_fs3.readFileSync)(this.baselinesPath, "utf-8");
|
|
6232
|
+
const data = JSON.parse(raw);
|
|
6233
|
+
const parsed = ArchBaselineSchema.safeParse(data);
|
|
6234
|
+
if (!parsed.success) {
|
|
6235
|
+
console.error(
|
|
6236
|
+
`Baseline validation failed for ${this.baselinesPath}:`,
|
|
6237
|
+
parsed.error.format()
|
|
6238
|
+
);
|
|
6239
|
+
return null;
|
|
6240
|
+
}
|
|
6241
|
+
return parsed.data;
|
|
6242
|
+
} catch (error) {
|
|
6243
|
+
console.error(`Error loading baseline from ${this.baselinesPath}:`, error);
|
|
6244
|
+
return null;
|
|
6245
|
+
}
|
|
6246
|
+
}
|
|
6247
|
+
/**
|
|
6248
|
+
* Save an ArchBaseline to disk.
|
|
6249
|
+
* Creates parent directories if they do not exist.
|
|
6250
|
+
* Uses atomic write (write to temp file, then rename) to prevent corruption.
|
|
6251
|
+
*/
|
|
6252
|
+
save(baseline) {
|
|
6253
|
+
const dir = (0, import_node_path10.dirname)(this.baselinesPath);
|
|
6254
|
+
if (!(0, import_node_fs3.existsSync)(dir)) {
|
|
6255
|
+
(0, import_node_fs3.mkdirSync)(dir, { recursive: true });
|
|
6256
|
+
}
|
|
6257
|
+
const tmp = this.baselinesPath + "." + (0, import_node_crypto2.randomBytes)(4).toString("hex") + ".tmp";
|
|
6258
|
+
(0, import_node_fs3.writeFileSync)(tmp, JSON.stringify(baseline, null, 2));
|
|
6259
|
+
(0, import_node_fs3.renameSync)(tmp, this.baselinesPath);
|
|
6260
|
+
}
|
|
6261
|
+
};
|
|
6262
|
+
|
|
6263
|
+
// src/architecture/diff.ts
|
|
6264
|
+
function aggregateByCategory(results) {
|
|
6265
|
+
const map = /* @__PURE__ */ new Map();
|
|
6266
|
+
for (const result of results) {
|
|
6267
|
+
const existing = map.get(result.category);
|
|
6268
|
+
if (existing) {
|
|
6269
|
+
existing.value += result.value;
|
|
6270
|
+
existing.violations.push(...result.violations);
|
|
6271
|
+
} else {
|
|
6272
|
+
map.set(result.category, {
|
|
6273
|
+
value: result.value,
|
|
6274
|
+
violations: [...result.violations]
|
|
6275
|
+
});
|
|
6276
|
+
}
|
|
6277
|
+
}
|
|
6278
|
+
return map;
|
|
6279
|
+
}
|
|
6280
|
+
function diff(current, baseline) {
|
|
6281
|
+
const aggregated = aggregateByCategory(current);
|
|
6282
|
+
const newViolations = [];
|
|
6283
|
+
const resolvedViolations = [];
|
|
6284
|
+
const preExisting = [];
|
|
6285
|
+
const regressions = [];
|
|
6286
|
+
const visitedCategories = /* @__PURE__ */ new Set();
|
|
6287
|
+
for (const [category, agg] of aggregated) {
|
|
6288
|
+
visitedCategories.add(category);
|
|
6289
|
+
const baselineCategory = baseline.metrics[category];
|
|
6290
|
+
const baselineViolationIds = new Set(baselineCategory?.violationIds ?? []);
|
|
6291
|
+
const baselineValue = baselineCategory?.value ?? 0;
|
|
6292
|
+
for (const violation of agg.violations) {
|
|
6293
|
+
if (baselineViolationIds.has(violation.id)) {
|
|
6294
|
+
preExisting.push(violation.id);
|
|
6295
|
+
} else {
|
|
6296
|
+
newViolations.push(violation);
|
|
6297
|
+
}
|
|
6298
|
+
}
|
|
6299
|
+
const currentViolationIds = new Set(agg.violations.map((v) => v.id));
|
|
6300
|
+
if (baselineCategory) {
|
|
6301
|
+
for (const id of baselineCategory.violationIds) {
|
|
6302
|
+
if (!currentViolationIds.has(id)) {
|
|
6303
|
+
resolvedViolations.push(id);
|
|
6304
|
+
}
|
|
6305
|
+
}
|
|
6306
|
+
}
|
|
6307
|
+
if (baselineCategory && agg.value > baselineValue) {
|
|
6308
|
+
regressions.push({
|
|
6309
|
+
category,
|
|
6310
|
+
baselineValue,
|
|
6311
|
+
currentValue: agg.value,
|
|
6312
|
+
delta: agg.value - baselineValue
|
|
6313
|
+
});
|
|
6314
|
+
}
|
|
6315
|
+
}
|
|
6316
|
+
for (const [category, baselineCategory] of Object.entries(baseline.metrics)) {
|
|
6317
|
+
if (!visitedCategories.has(category) && baselineCategory) {
|
|
6318
|
+
for (const id of baselineCategory.violationIds) {
|
|
6319
|
+
resolvedViolations.push(id);
|
|
6320
|
+
}
|
|
6321
|
+
}
|
|
6322
|
+
}
|
|
6323
|
+
const passed = newViolations.length === 0 && regressions.length === 0;
|
|
6324
|
+
return {
|
|
6325
|
+
passed,
|
|
6326
|
+
newViolations,
|
|
6327
|
+
resolvedViolations,
|
|
6328
|
+
preExisting,
|
|
6329
|
+
regressions
|
|
6330
|
+
};
|
|
6331
|
+
}
|
|
6332
|
+
|
|
6333
|
+
// src/architecture/config.ts
|
|
6334
|
+
function resolveThresholds(scope, config) {
|
|
6335
|
+
const projectThresholds = {};
|
|
6336
|
+
for (const [key, val] of Object.entries(config.thresholds)) {
|
|
6337
|
+
projectThresholds[key] = typeof val === "object" && val !== null && !Array.isArray(val) ? { ...val } : val;
|
|
6338
|
+
}
|
|
6339
|
+
if (scope === "project") {
|
|
6340
|
+
return projectThresholds;
|
|
6341
|
+
}
|
|
6342
|
+
const moduleOverrides = config.modules[scope];
|
|
6343
|
+
if (!moduleOverrides) {
|
|
6344
|
+
return projectThresholds;
|
|
6345
|
+
}
|
|
6346
|
+
const merged = { ...projectThresholds };
|
|
6347
|
+
for (const [category, moduleValue] of Object.entries(moduleOverrides)) {
|
|
6348
|
+
const projectValue = projectThresholds[category];
|
|
6349
|
+
if (projectValue !== void 0 && typeof projectValue === "object" && !Array.isArray(projectValue) && typeof moduleValue === "object" && !Array.isArray(moduleValue)) {
|
|
6350
|
+
merged[category] = {
|
|
6351
|
+
...projectValue,
|
|
6352
|
+
...moduleValue
|
|
6353
|
+
};
|
|
6354
|
+
} else {
|
|
6355
|
+
merged[category] = moduleValue;
|
|
6356
|
+
}
|
|
6357
|
+
}
|
|
6358
|
+
return merged;
|
|
6359
|
+
}
|
|
6360
|
+
|
|
6361
|
+
// src/architecture/matchers.ts
|
|
6362
|
+
function architecture(options) {
|
|
6363
|
+
return {
|
|
6364
|
+
kind: "arch-handle",
|
|
6365
|
+
scope: "project",
|
|
6366
|
+
rootDir: options?.rootDir ?? process.cwd(),
|
|
6367
|
+
config: options?.config
|
|
6368
|
+
};
|
|
6369
|
+
}
|
|
6370
|
+
function archModule(modulePath, options) {
|
|
6371
|
+
return {
|
|
6372
|
+
kind: "arch-handle",
|
|
6373
|
+
scope: modulePath,
|
|
6374
|
+
rootDir: options?.rootDir ?? process.cwd(),
|
|
6375
|
+
config: options?.config
|
|
6376
|
+
};
|
|
6377
|
+
}
|
|
6378
|
+
function resolveConfig(handle) {
|
|
6379
|
+
return ArchConfigSchema.parse(handle.config ?? {});
|
|
6380
|
+
}
|
|
6381
|
+
async function collectCategory(handle, collector) {
|
|
6382
|
+
if ("_mockResults" in handle && handle._mockResults) {
|
|
6383
|
+
return handle._mockResults;
|
|
6384
|
+
}
|
|
6385
|
+
const config = resolveConfig(handle);
|
|
6386
|
+
return collector.collect(config, handle.rootDir);
|
|
6387
|
+
}
|
|
6388
|
+
function formatViolationList(violations, limit = 10) {
|
|
6389
|
+
const lines = violations.slice(0, limit).map((v) => ` - ${v.file}: ${v.detail}`);
|
|
6390
|
+
if (violations.length > limit) {
|
|
6391
|
+
lines.push(` ... and ${violations.length - limit} more`);
|
|
6392
|
+
}
|
|
6393
|
+
return lines.join("\n");
|
|
6394
|
+
}
|
|
6395
|
+
async function toHaveNoCircularDeps(received) {
|
|
6396
|
+
const results = await collectCategory(received, new CircularDepsCollector());
|
|
6397
|
+
const violations = results.flatMap((r) => r.violations);
|
|
6398
|
+
const pass = violations.length === 0;
|
|
6399
|
+
return {
|
|
6400
|
+
pass,
|
|
6401
|
+
message: () => pass ? "Expected circular dependencies but found none" : `Found ${violations.length} circular dependenc${violations.length === 1 ? "y" : "ies"}:
|
|
6402
|
+
${formatViolationList(violations)}`
|
|
6403
|
+
};
|
|
6404
|
+
}
|
|
6405
|
+
async function toHaveNoLayerViolations(received) {
|
|
6406
|
+
const results = await collectCategory(received, new LayerViolationCollector());
|
|
6407
|
+
const violations = results.flatMap((r) => r.violations);
|
|
6408
|
+
const pass = violations.length === 0;
|
|
6409
|
+
return {
|
|
6410
|
+
pass,
|
|
6411
|
+
message: () => pass ? "Expected layer violations but found none" : `Found ${violations.length} layer violation${violations.length === 1 ? "" : "s"}:
|
|
6412
|
+
${formatViolationList(violations)}`
|
|
6413
|
+
};
|
|
6414
|
+
}
|
|
6415
|
+
async function toMatchBaseline(received, options) {
|
|
6416
|
+
let diffResult;
|
|
6417
|
+
if ("_mockDiff" in received && received._mockDiff) {
|
|
6418
|
+
diffResult = received._mockDiff;
|
|
6419
|
+
} else {
|
|
6420
|
+
const config = resolveConfig(received);
|
|
6421
|
+
const results = await runAll(config, received.rootDir);
|
|
6422
|
+
const manager = new ArchBaselineManager(received.rootDir, config.baselinePath);
|
|
6423
|
+
const baseline = manager.load();
|
|
6424
|
+
if (!baseline) {
|
|
6425
|
+
return {
|
|
6426
|
+
pass: false,
|
|
6427
|
+
message: () => "No baseline found. Run `harness check-arch --update-baseline` to create one."
|
|
6428
|
+
};
|
|
6429
|
+
}
|
|
6430
|
+
diffResult = diff(results, baseline);
|
|
6431
|
+
}
|
|
6432
|
+
const tolerance = options?.tolerance ?? 0;
|
|
6433
|
+
const effectiveNewCount = Math.max(0, diffResult.newViolations.length - tolerance);
|
|
6434
|
+
const pass = effectiveNewCount === 0 && diffResult.regressions.length === 0;
|
|
6435
|
+
return {
|
|
6436
|
+
pass,
|
|
6437
|
+
message: () => {
|
|
6438
|
+
if (pass) {
|
|
6439
|
+
return "Expected baseline regression but architecture matches baseline";
|
|
6440
|
+
}
|
|
6441
|
+
const parts = [];
|
|
6442
|
+
if (diffResult.newViolations.length > 0) {
|
|
6443
|
+
parts.push(
|
|
6444
|
+
`${diffResult.newViolations.length} new violation${diffResult.newViolations.length === 1 ? "" : "s"}${tolerance > 0 ? ` (tolerance: ${tolerance})` : ""}:
|
|
6445
|
+
${formatViolationList(diffResult.newViolations)}`
|
|
6446
|
+
);
|
|
6447
|
+
}
|
|
6448
|
+
if (diffResult.regressions.length > 0) {
|
|
6449
|
+
const regLines = diffResult.regressions.map(
|
|
6450
|
+
(r) => ` - ${r.category}: ${r.baselineValue} -> ${r.currentValue} (+${r.delta})`
|
|
6451
|
+
);
|
|
6452
|
+
parts.push(`Regressions:
|
|
6453
|
+
${regLines.join("\n")}`);
|
|
6454
|
+
}
|
|
6455
|
+
return `Baseline check failed:
|
|
6456
|
+
${parts.join("\n\n")}`;
|
|
6457
|
+
}
|
|
6458
|
+
};
|
|
6459
|
+
}
|
|
6460
|
+
function filterByScope(results, scope) {
|
|
6461
|
+
return results.filter(
|
|
6462
|
+
(r) => r.scope === scope || r.scope.startsWith(scope + "/") || r.scope === "project"
|
|
6463
|
+
);
|
|
6464
|
+
}
|
|
6465
|
+
async function toHaveMaxComplexity(received, maxComplexity) {
|
|
6466
|
+
const results = await collectCategory(received, new ComplexityCollector());
|
|
6467
|
+
const scoped = filterByScope(results, received.scope);
|
|
6468
|
+
const violations = scoped.flatMap((r) => r.violations);
|
|
6469
|
+
const totalValue = scoped.reduce((sum, r) => sum + r.value, 0);
|
|
6470
|
+
const pass = totalValue <= maxComplexity && violations.length === 0;
|
|
6471
|
+
return {
|
|
6472
|
+
pass,
|
|
6473
|
+
message: () => pass ? `Expected complexity to exceed ${maxComplexity} but it was within limits` : `Module '${received.scope}' has complexity violations (${violations.length} violation${violations.length === 1 ? "" : "s"}):
|
|
6474
|
+
${formatViolationList(violations)}`
|
|
6475
|
+
};
|
|
6476
|
+
}
|
|
6477
|
+
async function toHaveMaxCoupling(received, limits) {
|
|
6478
|
+
const config = resolveConfig(received);
|
|
6479
|
+
if (limits.fanIn !== void 0 || limits.fanOut !== void 0) {
|
|
6480
|
+
config.thresholds.coupling = {
|
|
6481
|
+
...typeof config.thresholds.coupling === "object" ? config.thresholds.coupling : {},
|
|
6482
|
+
...limits.fanIn !== void 0 ? { maxFanIn: limits.fanIn } : {},
|
|
6483
|
+
...limits.fanOut !== void 0 ? { maxFanOut: limits.fanOut } : {}
|
|
6484
|
+
};
|
|
6485
|
+
}
|
|
6486
|
+
const collector = new CouplingCollector();
|
|
6487
|
+
const results = "_mockResults" in received && received._mockResults ? received._mockResults : await collector.collect(config, received.rootDir);
|
|
6488
|
+
const scoped = filterByScope(results, received.scope);
|
|
6489
|
+
const violations = scoped.flatMap((r) => r.violations);
|
|
6490
|
+
const pass = violations.length === 0;
|
|
6491
|
+
return {
|
|
6492
|
+
pass,
|
|
6493
|
+
message: () => pass ? `Expected coupling violations in '${received.scope}' but found none` : `Module '${received.scope}' has ${violations.length} coupling violation${violations.length === 1 ? "" : "s"} (fanIn limit: ${limits.fanIn ?? "none"}, fanOut limit: ${limits.fanOut ?? "none"}):
|
|
6494
|
+
${formatViolationList(violations)}`
|
|
6495
|
+
};
|
|
6496
|
+
}
|
|
6497
|
+
async function toHaveMaxFileCount(received, maxFiles) {
|
|
6498
|
+
const results = await collectCategory(received, new ModuleSizeCollector());
|
|
6499
|
+
const scoped = filterByScope(results, received.scope);
|
|
6500
|
+
const fileCount = scoped.reduce((max, r) => {
|
|
6501
|
+
const meta = r.metadata;
|
|
6502
|
+
const fc = typeof meta?.fileCount === "number" ? meta.fileCount : 0;
|
|
6503
|
+
return fc > max ? fc : max;
|
|
6504
|
+
}, 0);
|
|
6505
|
+
const pass = fileCount <= maxFiles;
|
|
6506
|
+
return {
|
|
6507
|
+
pass,
|
|
6508
|
+
message: () => pass ? `Expected file count in '${received.scope}' to exceed ${maxFiles} but it was ${fileCount}` : `Module '${received.scope}' has ${fileCount} files (limit: ${maxFiles})`
|
|
6509
|
+
};
|
|
6510
|
+
}
|
|
6511
|
+
async function toNotDependOn(received, forbiddenModule) {
|
|
6512
|
+
const results = await collectCategory(received, new ForbiddenImportCollector());
|
|
6513
|
+
const allViolations = results.flatMap((r) => r.violations);
|
|
6514
|
+
const scopePrefix = received.scope.replace(/\/+$/, "");
|
|
6515
|
+
const forbiddenPrefix = forbiddenModule.replace(/\/+$/, "");
|
|
6516
|
+
const relevantViolations = allViolations.filter(
|
|
6517
|
+
(v) => (v.file === scopePrefix || v.file.startsWith(scopePrefix + "/")) && (v.detail.includes(forbiddenPrefix + "/") || v.detail.endsWith(forbiddenPrefix))
|
|
6518
|
+
);
|
|
6519
|
+
const pass = relevantViolations.length === 0;
|
|
6520
|
+
return {
|
|
6521
|
+
pass,
|
|
6522
|
+
message: () => pass ? `Expected '${received.scope}' to depend on '${forbiddenModule}' but no such imports found` : `Module '${received.scope}' depends on '${forbiddenModule}' (${relevantViolations.length} import${relevantViolations.length === 1 ? "" : "s"}):
|
|
6523
|
+
${formatViolationList(relevantViolations)}`
|
|
6524
|
+
};
|
|
6525
|
+
}
|
|
6526
|
+
async function toHaveMaxDepDepth(received, maxDepth) {
|
|
6527
|
+
const results = await collectCategory(received, new DepDepthCollector());
|
|
6528
|
+
const scoped = filterByScope(results, received.scope);
|
|
6529
|
+
const maxActual = scoped.reduce((max, r) => r.value > max ? r.value : max, 0);
|
|
6530
|
+
const pass = maxActual <= maxDepth;
|
|
6531
|
+
return {
|
|
6532
|
+
pass,
|
|
6533
|
+
message: () => pass ? `Expected dependency depth in '${received.scope}' to exceed ${maxDepth} but it was ${maxActual}` : `Module '${received.scope}' has dependency depth ${maxActual} (limit: ${maxDepth})`
|
|
6534
|
+
};
|
|
6535
|
+
}
|
|
6536
|
+
var archMatchers = {
|
|
6537
|
+
toHaveNoCircularDeps,
|
|
6538
|
+
toHaveNoLayerViolations,
|
|
6539
|
+
toMatchBaseline,
|
|
6540
|
+
toHaveMaxComplexity,
|
|
6541
|
+
toHaveMaxCoupling,
|
|
6542
|
+
toHaveMaxFileCount,
|
|
6543
|
+
toNotDependOn,
|
|
6544
|
+
toHaveMaxDepDepth
|
|
6545
|
+
};
|
|
6546
|
+
|
|
4894
6547
|
// src/state/types.ts
|
|
4895
|
-
var
|
|
4896
|
-
var FailureEntrySchema =
|
|
4897
|
-
date:
|
|
4898
|
-
skill:
|
|
4899
|
-
type:
|
|
4900
|
-
description:
|
|
6548
|
+
var import_zod4 = require("zod");
|
|
6549
|
+
var FailureEntrySchema = import_zod4.z.object({
|
|
6550
|
+
date: import_zod4.z.string(),
|
|
6551
|
+
skill: import_zod4.z.string(),
|
|
6552
|
+
type: import_zod4.z.string(),
|
|
6553
|
+
description: import_zod4.z.string()
|
|
4901
6554
|
});
|
|
4902
|
-
var HandoffSchema =
|
|
4903
|
-
timestamp:
|
|
4904
|
-
fromSkill:
|
|
4905
|
-
phase:
|
|
4906
|
-
summary:
|
|
4907
|
-
completed:
|
|
4908
|
-
pending:
|
|
4909
|
-
concerns:
|
|
4910
|
-
decisions:
|
|
4911
|
-
|
|
4912
|
-
what:
|
|
4913
|
-
why:
|
|
6555
|
+
var HandoffSchema = import_zod4.z.object({
|
|
6556
|
+
timestamp: import_zod4.z.string(),
|
|
6557
|
+
fromSkill: import_zod4.z.string(),
|
|
6558
|
+
phase: import_zod4.z.string(),
|
|
6559
|
+
summary: import_zod4.z.string(),
|
|
6560
|
+
completed: import_zod4.z.array(import_zod4.z.string()).default([]),
|
|
6561
|
+
pending: import_zod4.z.array(import_zod4.z.string()).default([]),
|
|
6562
|
+
concerns: import_zod4.z.array(import_zod4.z.string()).default([]),
|
|
6563
|
+
decisions: import_zod4.z.array(
|
|
6564
|
+
import_zod4.z.object({
|
|
6565
|
+
what: import_zod4.z.string(),
|
|
6566
|
+
why: import_zod4.z.string()
|
|
4914
6567
|
})
|
|
4915
6568
|
).default([]),
|
|
4916
|
-
blockers:
|
|
4917
|
-
contextKeywords:
|
|
6569
|
+
blockers: import_zod4.z.array(import_zod4.z.string()).default([]),
|
|
6570
|
+
contextKeywords: import_zod4.z.array(import_zod4.z.string()).default([])
|
|
4918
6571
|
});
|
|
4919
|
-
var GateCheckSchema =
|
|
4920
|
-
name:
|
|
4921
|
-
passed:
|
|
4922
|
-
command:
|
|
4923
|
-
output:
|
|
4924
|
-
duration:
|
|
6572
|
+
var GateCheckSchema = import_zod4.z.object({
|
|
6573
|
+
name: import_zod4.z.string(),
|
|
6574
|
+
passed: import_zod4.z.boolean(),
|
|
6575
|
+
command: import_zod4.z.string(),
|
|
6576
|
+
output: import_zod4.z.string().optional(),
|
|
6577
|
+
duration: import_zod4.z.number().optional()
|
|
4925
6578
|
});
|
|
4926
|
-
var GateResultSchema =
|
|
4927
|
-
passed:
|
|
4928
|
-
checks:
|
|
6579
|
+
var GateResultSchema = import_zod4.z.object({
|
|
6580
|
+
passed: import_zod4.z.boolean(),
|
|
6581
|
+
checks: import_zod4.z.array(GateCheckSchema)
|
|
4929
6582
|
});
|
|
4930
|
-
var GateConfigSchema =
|
|
4931
|
-
checks:
|
|
4932
|
-
|
|
4933
|
-
name:
|
|
4934
|
-
command:
|
|
6583
|
+
var GateConfigSchema = import_zod4.z.object({
|
|
6584
|
+
checks: import_zod4.z.array(
|
|
6585
|
+
import_zod4.z.object({
|
|
6586
|
+
name: import_zod4.z.string(),
|
|
6587
|
+
command: import_zod4.z.string()
|
|
4935
6588
|
})
|
|
4936
6589
|
).optional(),
|
|
4937
|
-
trace:
|
|
6590
|
+
trace: import_zod4.z.boolean().optional()
|
|
4938
6591
|
});
|
|
4939
|
-
var HarnessStateSchema =
|
|
4940
|
-
schemaVersion:
|
|
4941
|
-
position:
|
|
4942
|
-
phase:
|
|
4943
|
-
task:
|
|
6592
|
+
var HarnessStateSchema = import_zod4.z.object({
|
|
6593
|
+
schemaVersion: import_zod4.z.literal(1),
|
|
6594
|
+
position: import_zod4.z.object({
|
|
6595
|
+
phase: import_zod4.z.string().optional(),
|
|
6596
|
+
task: import_zod4.z.string().optional()
|
|
4944
6597
|
}).default({}),
|
|
4945
|
-
decisions:
|
|
4946
|
-
|
|
4947
|
-
date:
|
|
4948
|
-
decision:
|
|
4949
|
-
context:
|
|
6598
|
+
decisions: import_zod4.z.array(
|
|
6599
|
+
import_zod4.z.object({
|
|
6600
|
+
date: import_zod4.z.string(),
|
|
6601
|
+
decision: import_zod4.z.string(),
|
|
6602
|
+
context: import_zod4.z.string()
|
|
4950
6603
|
})
|
|
4951
6604
|
).default([]),
|
|
4952
|
-
blockers:
|
|
4953
|
-
|
|
4954
|
-
id:
|
|
4955
|
-
description:
|
|
4956
|
-
status:
|
|
6605
|
+
blockers: import_zod4.z.array(
|
|
6606
|
+
import_zod4.z.object({
|
|
6607
|
+
id: import_zod4.z.string(),
|
|
6608
|
+
description: import_zod4.z.string(),
|
|
6609
|
+
status: import_zod4.z.enum(["open", "resolved"])
|
|
4957
6610
|
})
|
|
4958
6611
|
).default([]),
|
|
4959
|
-
progress:
|
|
4960
|
-
lastSession:
|
|
4961
|
-
date:
|
|
4962
|
-
summary:
|
|
4963
|
-
lastSkill:
|
|
4964
|
-
pendingTasks:
|
|
6612
|
+
progress: import_zod4.z.record(import_zod4.z.enum(["pending", "in_progress", "complete"])).default({}),
|
|
6613
|
+
lastSession: import_zod4.z.object({
|
|
6614
|
+
date: import_zod4.z.string(),
|
|
6615
|
+
summary: import_zod4.z.string(),
|
|
6616
|
+
lastSkill: import_zod4.z.string().optional(),
|
|
6617
|
+
pendingTasks: import_zod4.z.array(import_zod4.z.string()).optional()
|
|
4965
6618
|
}).optional()
|
|
4966
6619
|
});
|
|
4967
6620
|
var DEFAULT_STATE = {
|
|
@@ -4973,27 +6626,27 @@ var DEFAULT_STATE = {
|
|
|
4973
6626
|
};
|
|
4974
6627
|
|
|
4975
6628
|
// src/state/state-manager.ts
|
|
4976
|
-
var
|
|
6629
|
+
var fs6 = __toESM(require("fs"));
|
|
4977
6630
|
var path3 = __toESM(require("path"));
|
|
4978
6631
|
var import_child_process2 = require("child_process");
|
|
4979
6632
|
|
|
4980
6633
|
// src/state/stream-resolver.ts
|
|
4981
|
-
var
|
|
6634
|
+
var fs5 = __toESM(require("fs"));
|
|
4982
6635
|
var path2 = __toESM(require("path"));
|
|
4983
6636
|
var import_child_process = require("child_process");
|
|
4984
6637
|
|
|
4985
6638
|
// src/state/stream-types.ts
|
|
4986
|
-
var
|
|
4987
|
-
var StreamInfoSchema =
|
|
4988
|
-
name:
|
|
4989
|
-
branch:
|
|
4990
|
-
createdAt:
|
|
4991
|
-
lastActiveAt:
|
|
6639
|
+
var import_zod5 = require("zod");
|
|
6640
|
+
var StreamInfoSchema = import_zod5.z.object({
|
|
6641
|
+
name: import_zod5.z.string(),
|
|
6642
|
+
branch: import_zod5.z.string().optional(),
|
|
6643
|
+
createdAt: import_zod5.z.string(),
|
|
6644
|
+
lastActiveAt: import_zod5.z.string()
|
|
4992
6645
|
});
|
|
4993
|
-
var StreamIndexSchema =
|
|
4994
|
-
schemaVersion:
|
|
4995
|
-
activeStream:
|
|
4996
|
-
streams:
|
|
6646
|
+
var StreamIndexSchema = import_zod5.z.object({
|
|
6647
|
+
schemaVersion: import_zod5.z.literal(1),
|
|
6648
|
+
activeStream: import_zod5.z.string().nullable(),
|
|
6649
|
+
streams: import_zod5.z.record(StreamInfoSchema)
|
|
4997
6650
|
});
|
|
4998
6651
|
var DEFAULT_STREAM_INDEX = {
|
|
4999
6652
|
schemaVersion: 1,
|
|
@@ -5024,11 +6677,11 @@ function validateStreamName(name) {
|
|
|
5024
6677
|
}
|
|
5025
6678
|
async function loadStreamIndex(projectPath) {
|
|
5026
6679
|
const idxPath = indexPath(projectPath);
|
|
5027
|
-
if (!
|
|
6680
|
+
if (!fs5.existsSync(idxPath)) {
|
|
5028
6681
|
return (0, import_types.Ok)({ ...DEFAULT_STREAM_INDEX, streams: {} });
|
|
5029
6682
|
}
|
|
5030
6683
|
try {
|
|
5031
|
-
const raw =
|
|
6684
|
+
const raw = fs5.readFileSync(idxPath, "utf-8");
|
|
5032
6685
|
const parsed = JSON.parse(raw);
|
|
5033
6686
|
const result = StreamIndexSchema.safeParse(parsed);
|
|
5034
6687
|
if (!result.success) {
|
|
@@ -5046,8 +6699,8 @@ async function loadStreamIndex(projectPath) {
|
|
|
5046
6699
|
async function saveStreamIndex(projectPath, index) {
|
|
5047
6700
|
const dir = streamsDir(projectPath);
|
|
5048
6701
|
try {
|
|
5049
|
-
|
|
5050
|
-
|
|
6702
|
+
fs5.mkdirSync(dir, { recursive: true });
|
|
6703
|
+
fs5.writeFileSync(indexPath(projectPath), JSON.stringify(index, null, 2));
|
|
5051
6704
|
return (0, import_types.Ok)(void 0);
|
|
5052
6705
|
} catch (error) {
|
|
5053
6706
|
return (0, import_types.Err)(
|
|
@@ -5129,7 +6782,7 @@ async function createStream(projectPath, name, branch) {
|
|
|
5129
6782
|
}
|
|
5130
6783
|
const streamPath = path2.join(streamsDir(projectPath), name);
|
|
5131
6784
|
try {
|
|
5132
|
-
|
|
6785
|
+
fs5.mkdirSync(streamPath, { recursive: true });
|
|
5133
6786
|
} catch (error) {
|
|
5134
6787
|
return (0, import_types.Err)(
|
|
5135
6788
|
new Error(
|
|
@@ -5173,9 +6826,9 @@ async function archiveStream(projectPath, name) {
|
|
|
5173
6826
|
const streamPath = path2.join(streamsDir(projectPath), name);
|
|
5174
6827
|
const archiveDir = path2.join(projectPath, HARNESS_DIR, "archive", "streams");
|
|
5175
6828
|
try {
|
|
5176
|
-
|
|
6829
|
+
fs5.mkdirSync(archiveDir, { recursive: true });
|
|
5177
6830
|
const date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
5178
|
-
|
|
6831
|
+
fs5.renameSync(streamPath, path2.join(archiveDir, `${name}-${date}`));
|
|
5179
6832
|
} catch (error) {
|
|
5180
6833
|
return (0, import_types.Err)(
|
|
5181
6834
|
new Error(
|
|
@@ -5198,18 +6851,18 @@ function getStreamForBranch(index, branch) {
|
|
|
5198
6851
|
var STATE_FILES = ["state.json", "handoff.json", "learnings.md", "failures.md"];
|
|
5199
6852
|
async function migrateToStreams(projectPath) {
|
|
5200
6853
|
const harnessDir = path2.join(projectPath, HARNESS_DIR);
|
|
5201
|
-
if (
|
|
6854
|
+
if (fs5.existsSync(indexPath(projectPath))) {
|
|
5202
6855
|
return (0, import_types.Ok)(void 0);
|
|
5203
6856
|
}
|
|
5204
|
-
const filesToMove = STATE_FILES.filter((f) =>
|
|
6857
|
+
const filesToMove = STATE_FILES.filter((f) => fs5.existsSync(path2.join(harnessDir, f)));
|
|
5205
6858
|
if (filesToMove.length === 0) {
|
|
5206
6859
|
return (0, import_types.Ok)(void 0);
|
|
5207
6860
|
}
|
|
5208
6861
|
const defaultDir = path2.join(streamsDir(projectPath), "default");
|
|
5209
6862
|
try {
|
|
5210
|
-
|
|
6863
|
+
fs5.mkdirSync(defaultDir, { recursive: true });
|
|
5211
6864
|
for (const file of filesToMove) {
|
|
5212
|
-
|
|
6865
|
+
fs5.renameSync(path2.join(harnessDir, file), path2.join(defaultDir, file));
|
|
5213
6866
|
}
|
|
5214
6867
|
} catch (error) {
|
|
5215
6868
|
return (0, import_types.Err)(
|
|
@@ -5250,7 +6903,7 @@ function evictIfNeeded(map) {
|
|
|
5250
6903
|
}
|
|
5251
6904
|
async function getStateDir(projectPath, stream) {
|
|
5252
6905
|
const streamsIndexPath = path3.join(projectPath, HARNESS_DIR2, "streams", INDEX_FILE2);
|
|
5253
|
-
const hasStreams =
|
|
6906
|
+
const hasStreams = fs6.existsSync(streamsIndexPath);
|
|
5254
6907
|
if (stream || hasStreams) {
|
|
5255
6908
|
const result = await resolveStreamPath(projectPath, stream ? { stream } : void 0);
|
|
5256
6909
|
if (result.ok) {
|
|
@@ -5268,10 +6921,10 @@ async function loadState(projectPath, stream) {
|
|
|
5268
6921
|
if (!dirResult.ok) return dirResult;
|
|
5269
6922
|
const stateDir = dirResult.value;
|
|
5270
6923
|
const statePath = path3.join(stateDir, STATE_FILE);
|
|
5271
|
-
if (!
|
|
6924
|
+
if (!fs6.existsSync(statePath)) {
|
|
5272
6925
|
return (0, import_types.Ok)({ ...DEFAULT_STATE });
|
|
5273
6926
|
}
|
|
5274
|
-
const raw =
|
|
6927
|
+
const raw = fs6.readFileSync(statePath, "utf-8");
|
|
5275
6928
|
const parsed = JSON.parse(raw);
|
|
5276
6929
|
const result = HarnessStateSchema.safeParse(parsed);
|
|
5277
6930
|
if (!result.success) {
|
|
@@ -5290,8 +6943,8 @@ async function saveState(projectPath, state, stream) {
|
|
|
5290
6943
|
if (!dirResult.ok) return dirResult;
|
|
5291
6944
|
const stateDir = dirResult.value;
|
|
5292
6945
|
const statePath = path3.join(stateDir, STATE_FILE);
|
|
5293
|
-
|
|
5294
|
-
|
|
6946
|
+
fs6.mkdirSync(stateDir, { recursive: true });
|
|
6947
|
+
fs6.writeFileSync(statePath, JSON.stringify(state, null, 2));
|
|
5295
6948
|
return (0, import_types.Ok)(void 0);
|
|
5296
6949
|
} catch (error) {
|
|
5297
6950
|
return (0, import_types.Err)(
|
|
@@ -5305,7 +6958,7 @@ async function appendLearning(projectPath, learning, skillName, outcome, stream)
|
|
|
5305
6958
|
if (!dirResult.ok) return dirResult;
|
|
5306
6959
|
const stateDir = dirResult.value;
|
|
5307
6960
|
const learningsPath = path3.join(stateDir, LEARNINGS_FILE);
|
|
5308
|
-
|
|
6961
|
+
fs6.mkdirSync(stateDir, { recursive: true });
|
|
5309
6962
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
5310
6963
|
let entry;
|
|
5311
6964
|
if (skillName && outcome) {
|
|
@@ -5321,11 +6974,11 @@ async function appendLearning(projectPath, learning, skillName, outcome, stream)
|
|
|
5321
6974
|
- **${timestamp}:** ${learning}
|
|
5322
6975
|
`;
|
|
5323
6976
|
}
|
|
5324
|
-
if (!
|
|
5325
|
-
|
|
6977
|
+
if (!fs6.existsSync(learningsPath)) {
|
|
6978
|
+
fs6.writeFileSync(learningsPath, `# Learnings
|
|
5326
6979
|
${entry}`);
|
|
5327
6980
|
} else {
|
|
5328
|
-
|
|
6981
|
+
fs6.appendFileSync(learningsPath, entry);
|
|
5329
6982
|
}
|
|
5330
6983
|
learningsCacheMap.delete(learningsPath);
|
|
5331
6984
|
return (0, import_types.Ok)(void 0);
|
|
@@ -5343,17 +6996,17 @@ async function loadRelevantLearnings(projectPath, skillName, stream) {
|
|
|
5343
6996
|
if (!dirResult.ok) return dirResult;
|
|
5344
6997
|
const stateDir = dirResult.value;
|
|
5345
6998
|
const learningsPath = path3.join(stateDir, LEARNINGS_FILE);
|
|
5346
|
-
if (!
|
|
6999
|
+
if (!fs6.existsSync(learningsPath)) {
|
|
5347
7000
|
return (0, import_types.Ok)([]);
|
|
5348
7001
|
}
|
|
5349
|
-
const stats =
|
|
7002
|
+
const stats = fs6.statSync(learningsPath);
|
|
5350
7003
|
const cacheKey = learningsPath;
|
|
5351
7004
|
const cached = learningsCacheMap.get(cacheKey);
|
|
5352
7005
|
let entries;
|
|
5353
7006
|
if (cached && cached.mtimeMs === stats.mtimeMs) {
|
|
5354
7007
|
entries = cached.entries;
|
|
5355
7008
|
} else {
|
|
5356
|
-
const content =
|
|
7009
|
+
const content = fs6.readFileSync(learningsPath, "utf-8");
|
|
5357
7010
|
const lines = content.split("\n");
|
|
5358
7011
|
entries = [];
|
|
5359
7012
|
let currentBlock = [];
|
|
@@ -5396,16 +7049,16 @@ async function appendFailure(projectPath, description, skillName, type, stream)
|
|
|
5396
7049
|
if (!dirResult.ok) return dirResult;
|
|
5397
7050
|
const stateDir = dirResult.value;
|
|
5398
7051
|
const failuresPath = path3.join(stateDir, FAILURES_FILE);
|
|
5399
|
-
|
|
7052
|
+
fs6.mkdirSync(stateDir, { recursive: true });
|
|
5400
7053
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
5401
7054
|
const entry = `
|
|
5402
7055
|
- **${timestamp} [skill:${skillName}] [type:${type}]:** ${description}
|
|
5403
7056
|
`;
|
|
5404
|
-
if (!
|
|
5405
|
-
|
|
7057
|
+
if (!fs6.existsSync(failuresPath)) {
|
|
7058
|
+
fs6.writeFileSync(failuresPath, `# Failures
|
|
5406
7059
|
${entry}`);
|
|
5407
7060
|
} else {
|
|
5408
|
-
|
|
7061
|
+
fs6.appendFileSync(failuresPath, entry);
|
|
5409
7062
|
}
|
|
5410
7063
|
failuresCacheMap.delete(failuresPath);
|
|
5411
7064
|
return (0, import_types.Ok)(void 0);
|
|
@@ -5423,16 +7076,16 @@ async function loadFailures(projectPath, stream) {
|
|
|
5423
7076
|
if (!dirResult.ok) return dirResult;
|
|
5424
7077
|
const stateDir = dirResult.value;
|
|
5425
7078
|
const failuresPath = path3.join(stateDir, FAILURES_FILE);
|
|
5426
|
-
if (!
|
|
7079
|
+
if (!fs6.existsSync(failuresPath)) {
|
|
5427
7080
|
return (0, import_types.Ok)([]);
|
|
5428
7081
|
}
|
|
5429
|
-
const stats =
|
|
7082
|
+
const stats = fs6.statSync(failuresPath);
|
|
5430
7083
|
const cacheKey = failuresPath;
|
|
5431
7084
|
const cached = failuresCacheMap.get(cacheKey);
|
|
5432
7085
|
if (cached && cached.mtimeMs === stats.mtimeMs) {
|
|
5433
7086
|
return (0, import_types.Ok)(cached.entries);
|
|
5434
7087
|
}
|
|
5435
|
-
const content =
|
|
7088
|
+
const content = fs6.readFileSync(failuresPath, "utf-8");
|
|
5436
7089
|
const entries = [];
|
|
5437
7090
|
for (const line of content.split("\n")) {
|
|
5438
7091
|
const match = line.match(FAILURE_LINE_REGEX);
|
|
@@ -5462,19 +7115,19 @@ async function archiveFailures(projectPath, stream) {
|
|
|
5462
7115
|
if (!dirResult.ok) return dirResult;
|
|
5463
7116
|
const stateDir = dirResult.value;
|
|
5464
7117
|
const failuresPath = path3.join(stateDir, FAILURES_FILE);
|
|
5465
|
-
if (!
|
|
7118
|
+
if (!fs6.existsSync(failuresPath)) {
|
|
5466
7119
|
return (0, import_types.Ok)(void 0);
|
|
5467
7120
|
}
|
|
5468
7121
|
const archiveDir = path3.join(stateDir, "archive");
|
|
5469
|
-
|
|
7122
|
+
fs6.mkdirSync(archiveDir, { recursive: true });
|
|
5470
7123
|
const date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
5471
7124
|
let archiveName = `failures-${date}.md`;
|
|
5472
7125
|
let counter = 2;
|
|
5473
|
-
while (
|
|
7126
|
+
while (fs6.existsSync(path3.join(archiveDir, archiveName))) {
|
|
5474
7127
|
archiveName = `failures-${date}-${counter}.md`;
|
|
5475
7128
|
counter++;
|
|
5476
7129
|
}
|
|
5477
|
-
|
|
7130
|
+
fs6.renameSync(failuresPath, path3.join(archiveDir, archiveName));
|
|
5478
7131
|
failuresCacheMap.delete(failuresPath);
|
|
5479
7132
|
return (0, import_types.Ok)(void 0);
|
|
5480
7133
|
} catch (error) {
|
|
@@ -5491,8 +7144,8 @@ async function saveHandoff(projectPath, handoff, stream) {
|
|
|
5491
7144
|
if (!dirResult.ok) return dirResult;
|
|
5492
7145
|
const stateDir = dirResult.value;
|
|
5493
7146
|
const handoffPath = path3.join(stateDir, HANDOFF_FILE);
|
|
5494
|
-
|
|
5495
|
-
|
|
7147
|
+
fs6.mkdirSync(stateDir, { recursive: true });
|
|
7148
|
+
fs6.writeFileSync(handoffPath, JSON.stringify(handoff, null, 2));
|
|
5496
7149
|
return (0, import_types.Ok)(void 0);
|
|
5497
7150
|
} catch (error) {
|
|
5498
7151
|
return (0, import_types.Err)(
|
|
@@ -5506,10 +7159,10 @@ async function loadHandoff(projectPath, stream) {
|
|
|
5506
7159
|
if (!dirResult.ok) return dirResult;
|
|
5507
7160
|
const stateDir = dirResult.value;
|
|
5508
7161
|
const handoffPath = path3.join(stateDir, HANDOFF_FILE);
|
|
5509
|
-
if (!
|
|
7162
|
+
if (!fs6.existsSync(handoffPath)) {
|
|
5510
7163
|
return (0, import_types.Ok)(null);
|
|
5511
7164
|
}
|
|
5512
|
-
const raw =
|
|
7165
|
+
const raw = fs6.readFileSync(handoffPath, "utf-8");
|
|
5513
7166
|
const parsed = JSON.parse(raw);
|
|
5514
7167
|
const result = HandoffSchema.safeParse(parsed);
|
|
5515
7168
|
if (!result.success) {
|
|
@@ -5527,8 +7180,8 @@ async function runMechanicalGate(projectPath) {
|
|
|
5527
7180
|
const gateConfigPath = path3.join(harnessDir, GATE_CONFIG_FILE);
|
|
5528
7181
|
try {
|
|
5529
7182
|
let checks = [];
|
|
5530
|
-
if (
|
|
5531
|
-
const raw = JSON.parse(
|
|
7183
|
+
if (fs6.existsSync(gateConfigPath)) {
|
|
7184
|
+
const raw = JSON.parse(fs6.readFileSync(gateConfigPath, "utf-8"));
|
|
5532
7185
|
const config = GateConfigSchema.safeParse(raw);
|
|
5533
7186
|
if (config.success && config.data.checks) {
|
|
5534
7187
|
checks = config.data.checks;
|
|
@@ -5536,19 +7189,19 @@ async function runMechanicalGate(projectPath) {
|
|
|
5536
7189
|
}
|
|
5537
7190
|
if (checks.length === 0) {
|
|
5538
7191
|
const packageJsonPath = path3.join(projectPath, "package.json");
|
|
5539
|
-
if (
|
|
5540
|
-
const pkg = JSON.parse(
|
|
7192
|
+
if (fs6.existsSync(packageJsonPath)) {
|
|
7193
|
+
const pkg = JSON.parse(fs6.readFileSync(packageJsonPath, "utf-8"));
|
|
5541
7194
|
const scripts = pkg.scripts || {};
|
|
5542
7195
|
if (scripts.test) checks.push({ name: "test", command: "npm test" });
|
|
5543
7196
|
if (scripts.lint) checks.push({ name: "lint", command: "npm run lint" });
|
|
5544
7197
|
if (scripts.typecheck) checks.push({ name: "typecheck", command: "npm run typecheck" });
|
|
5545
7198
|
if (scripts.build) checks.push({ name: "build", command: "npm run build" });
|
|
5546
7199
|
}
|
|
5547
|
-
if (
|
|
7200
|
+
if (fs6.existsSync(path3.join(projectPath, "go.mod"))) {
|
|
5548
7201
|
checks.push({ name: "test", command: "go test ./..." });
|
|
5549
7202
|
checks.push({ name: "build", command: "go build ./..." });
|
|
5550
7203
|
}
|
|
5551
|
-
if (
|
|
7204
|
+
if (fs6.existsSync(path3.join(projectPath, "pyproject.toml")) || fs6.existsSync(path3.join(projectPath, "setup.py"))) {
|
|
5552
7205
|
checks.push({ name: "test", command: "python -m pytest" });
|
|
5553
7206
|
}
|
|
5554
7207
|
}
|
|
@@ -5751,7 +7404,7 @@ async function runMultiTurnPipeline(initialContext, turnExecutor, options) {
|
|
|
5751
7404
|
}
|
|
5752
7405
|
|
|
5753
7406
|
// src/security/scanner.ts
|
|
5754
|
-
var
|
|
7407
|
+
var fs8 = __toESM(require("fs/promises"));
|
|
5755
7408
|
|
|
5756
7409
|
// src/security/rules/registry.ts
|
|
5757
7410
|
var RuleRegistry = class {
|
|
@@ -5782,7 +7435,7 @@ var RuleRegistry = class {
|
|
|
5782
7435
|
};
|
|
5783
7436
|
|
|
5784
7437
|
// src/security/config.ts
|
|
5785
|
-
var
|
|
7438
|
+
var import_zod6 = require("zod");
|
|
5786
7439
|
|
|
5787
7440
|
// src/security/types.ts
|
|
5788
7441
|
var DEFAULT_SECURITY_CONFIG = {
|
|
@@ -5793,19 +7446,19 @@ var DEFAULT_SECURITY_CONFIG = {
|
|
|
5793
7446
|
};
|
|
5794
7447
|
|
|
5795
7448
|
// src/security/config.ts
|
|
5796
|
-
var RuleOverrideSchema =
|
|
5797
|
-
var SecurityConfigSchema =
|
|
5798
|
-
enabled:
|
|
5799
|
-
strict:
|
|
5800
|
-
rules:
|
|
5801
|
-
exclude:
|
|
5802
|
-
external:
|
|
5803
|
-
semgrep:
|
|
5804
|
-
enabled:
|
|
5805
|
-
rulesets:
|
|
7449
|
+
var RuleOverrideSchema = import_zod6.z.enum(["off", "error", "warning", "info"]);
|
|
7450
|
+
var SecurityConfigSchema = import_zod6.z.object({
|
|
7451
|
+
enabled: import_zod6.z.boolean().default(true),
|
|
7452
|
+
strict: import_zod6.z.boolean().default(false),
|
|
7453
|
+
rules: import_zod6.z.record(import_zod6.z.string(), RuleOverrideSchema).optional().default({}),
|
|
7454
|
+
exclude: import_zod6.z.array(import_zod6.z.string()).optional().default(["**/node_modules/**", "**/dist/**", "**/*.test.ts", "**/fixtures/**"]),
|
|
7455
|
+
external: import_zod6.z.object({
|
|
7456
|
+
semgrep: import_zod6.z.object({
|
|
7457
|
+
enabled: import_zod6.z.union([import_zod6.z.literal("auto"), import_zod6.z.boolean()]).default("auto"),
|
|
7458
|
+
rulesets: import_zod6.z.array(import_zod6.z.string()).optional()
|
|
5806
7459
|
}).optional(),
|
|
5807
|
-
gitleaks:
|
|
5808
|
-
enabled:
|
|
7460
|
+
gitleaks: import_zod6.z.object({
|
|
7461
|
+
enabled: import_zod6.z.union([import_zod6.z.literal("auto"), import_zod6.z.boolean()]).default("auto")
|
|
5809
7462
|
}).optional()
|
|
5810
7463
|
}).optional()
|
|
5811
7464
|
});
|
|
@@ -5838,15 +7491,15 @@ function resolveRuleSeverity(ruleId, defaultSeverity, overrides, strict) {
|
|
|
5838
7491
|
}
|
|
5839
7492
|
|
|
5840
7493
|
// src/security/stack-detector.ts
|
|
5841
|
-
var
|
|
7494
|
+
var fs7 = __toESM(require("fs"));
|
|
5842
7495
|
var path4 = __toESM(require("path"));
|
|
5843
7496
|
function detectStack(projectRoot) {
|
|
5844
7497
|
const stacks = [];
|
|
5845
7498
|
const pkgJsonPath = path4.join(projectRoot, "package.json");
|
|
5846
|
-
if (
|
|
7499
|
+
if (fs7.existsSync(pkgJsonPath)) {
|
|
5847
7500
|
stacks.push("node");
|
|
5848
7501
|
try {
|
|
5849
|
-
const pkgJson = JSON.parse(
|
|
7502
|
+
const pkgJson = JSON.parse(fs7.readFileSync(pkgJsonPath, "utf-8"));
|
|
5850
7503
|
const allDeps = {
|
|
5851
7504
|
...pkgJson.dependencies,
|
|
5852
7505
|
...pkgJson.devDependencies
|
|
@@ -5862,12 +7515,12 @@ function detectStack(projectRoot) {
|
|
|
5862
7515
|
}
|
|
5863
7516
|
}
|
|
5864
7517
|
const goModPath = path4.join(projectRoot, "go.mod");
|
|
5865
|
-
if (
|
|
7518
|
+
if (fs7.existsSync(goModPath)) {
|
|
5866
7519
|
stacks.push("go");
|
|
5867
7520
|
}
|
|
5868
7521
|
const requirementsPath = path4.join(projectRoot, "requirements.txt");
|
|
5869
7522
|
const pyprojectPath = path4.join(projectRoot, "pyproject.toml");
|
|
5870
|
-
if (
|
|
7523
|
+
if (fs7.existsSync(requirementsPath) || fs7.existsSync(pyprojectPath)) {
|
|
5871
7524
|
stacks.push("python");
|
|
5872
7525
|
}
|
|
5873
7526
|
return stacks;
|
|
@@ -6294,7 +7947,7 @@ var SecurityScanner = class {
|
|
|
6294
7947
|
}
|
|
6295
7948
|
async scanFile(filePath) {
|
|
6296
7949
|
if (!this.config.enabled) return [];
|
|
6297
|
-
const content = await
|
|
7950
|
+
const content = await fs8.readFile(filePath, "utf-8");
|
|
6298
7951
|
return this.scanContent(content, filePath, 1);
|
|
6299
7952
|
}
|
|
6300
7953
|
async scanFiles(filePaths) {
|
|
@@ -6327,7 +7980,8 @@ var ALL_CHECKS = [
|
|
|
6327
7980
|
"entropy",
|
|
6328
7981
|
"security",
|
|
6329
7982
|
"perf",
|
|
6330
|
-
"phase-gate"
|
|
7983
|
+
"phase-gate",
|
|
7984
|
+
"arch"
|
|
6331
7985
|
];
|
|
6332
7986
|
async function runSingleCheck(name, projectRoot, config) {
|
|
6333
7987
|
const start = Date.now();
|
|
@@ -6391,7 +8045,17 @@ async function runSingleCheck(name, projectRoot, config) {
|
|
|
6391
8045
|
}
|
|
6392
8046
|
case "docs": {
|
|
6393
8047
|
const docsDir = path5.join(projectRoot, config.docsDir ?? "docs");
|
|
6394
|
-
const
|
|
8048
|
+
const entropyConfig = config.entropy || {};
|
|
8049
|
+
const result = await checkDocCoverage("project", {
|
|
8050
|
+
docsDir,
|
|
8051
|
+
sourceDir: projectRoot,
|
|
8052
|
+
excludePatterns: entropyConfig.excludePatterns || [
|
|
8053
|
+
"**/node_modules/**",
|
|
8054
|
+
"**/dist/**",
|
|
8055
|
+
"**/*.test.ts",
|
|
8056
|
+
"**/fixtures/**"
|
|
8057
|
+
]
|
|
8058
|
+
});
|
|
6395
8059
|
if (!result.ok) {
|
|
6396
8060
|
issues.push({ severity: "warning", message: result.error.message });
|
|
6397
8061
|
} else if (result.value.gaps.length > 0) {
|
|
@@ -6466,11 +8130,13 @@ async function runSingleCheck(name, projectRoot, config) {
|
|
|
6466
8130
|
break;
|
|
6467
8131
|
}
|
|
6468
8132
|
case "perf": {
|
|
8133
|
+
const perfConfig = config.performance || {};
|
|
6469
8134
|
const perfAnalyzer = new EntropyAnalyzer({
|
|
6470
8135
|
rootDir: projectRoot,
|
|
6471
8136
|
analyze: {
|
|
6472
|
-
complexity: true,
|
|
6473
|
-
coupling: true
|
|
8137
|
+
complexity: perfConfig.complexity || true,
|
|
8138
|
+
coupling: perfConfig.coupling || true,
|
|
8139
|
+
sizeBudget: perfConfig.sizeBudget || false
|
|
6474
8140
|
}
|
|
6475
8141
|
});
|
|
6476
8142
|
const perfResult = await perfAnalyzer.analyze();
|
|
@@ -6511,6 +8177,43 @@ async function runSingleCheck(name, projectRoot, config) {
|
|
|
6511
8177
|
});
|
|
6512
8178
|
break;
|
|
6513
8179
|
}
|
|
8180
|
+
case "arch": {
|
|
8181
|
+
const rawArchConfig = config.architecture;
|
|
8182
|
+
const archConfig = ArchConfigSchema.parse(rawArchConfig ?? {});
|
|
8183
|
+
if (!archConfig.enabled) break;
|
|
8184
|
+
const results = await runAll(archConfig, projectRoot);
|
|
8185
|
+
const baselineManager = new ArchBaselineManager(projectRoot, archConfig.baselinePath);
|
|
8186
|
+
const baseline = baselineManager.load();
|
|
8187
|
+
if (baseline) {
|
|
8188
|
+
const diffResult = diff(results, baseline);
|
|
8189
|
+
if (!diffResult.passed) {
|
|
8190
|
+
for (const v of diffResult.newViolations) {
|
|
8191
|
+
issues.push({
|
|
8192
|
+
severity: v.severity,
|
|
8193
|
+
message: `[${v.category || "arch"}] NEW: ${v.detail}`,
|
|
8194
|
+
file: v.file
|
|
8195
|
+
});
|
|
8196
|
+
}
|
|
8197
|
+
for (const r of diffResult.regressions) {
|
|
8198
|
+
issues.push({
|
|
8199
|
+
severity: "error",
|
|
8200
|
+
message: `[${r.category}] REGRESSION: ${r.currentValue} > ${r.baselineValue} (delta: ${r.delta})`
|
|
8201
|
+
});
|
|
8202
|
+
}
|
|
8203
|
+
}
|
|
8204
|
+
} else {
|
|
8205
|
+
for (const result of results) {
|
|
8206
|
+
for (const v of result.violations) {
|
|
8207
|
+
issues.push({
|
|
8208
|
+
severity: v.severity,
|
|
8209
|
+
message: `[${result.category}] ${v.detail}`,
|
|
8210
|
+
file: v.file
|
|
8211
|
+
});
|
|
8212
|
+
}
|
|
8213
|
+
}
|
|
8214
|
+
}
|
|
8215
|
+
break;
|
|
8216
|
+
}
|
|
6514
8217
|
}
|
|
6515
8218
|
} catch (error) {
|
|
6516
8219
|
issues.push({
|
|
@@ -6845,22 +8548,22 @@ var PREFIX_PATTERNS = [
|
|
|
6845
8548
|
];
|
|
6846
8549
|
var TEST_FILE_PATTERN = /\.(test|spec)\.(ts|tsx|js|jsx|mts|cts)$/;
|
|
6847
8550
|
var MD_FILE_PATTERN = /\.md$/;
|
|
6848
|
-
function detectChangeType(commitMessage,
|
|
8551
|
+
function detectChangeType(commitMessage, diff2) {
|
|
6849
8552
|
const trimmed = commitMessage.trim();
|
|
6850
8553
|
for (const { pattern, type } of PREFIX_PATTERNS) {
|
|
6851
8554
|
if (pattern.test(trimmed)) {
|
|
6852
8555
|
return type;
|
|
6853
8556
|
}
|
|
6854
8557
|
}
|
|
6855
|
-
if (
|
|
8558
|
+
if (diff2.changedFiles.length > 0 && diff2.changedFiles.every((f) => MD_FILE_PATTERN.test(f))) {
|
|
6856
8559
|
return "docs";
|
|
6857
8560
|
}
|
|
6858
|
-
const newNonTestFiles =
|
|
8561
|
+
const newNonTestFiles = diff2.newFiles.filter((f) => !TEST_FILE_PATTERN.test(f));
|
|
6859
8562
|
if (newNonTestFiles.length > 0) {
|
|
6860
8563
|
return "feature";
|
|
6861
8564
|
}
|
|
6862
|
-
const hasNewTestFile =
|
|
6863
|
-
if (
|
|
8565
|
+
const hasNewTestFile = diff2.newFiles.some((f) => TEST_FILE_PATTERN.test(f));
|
|
8566
|
+
if (diff2.totalDiffLines < 20 && hasNewTestFile) {
|
|
6864
8567
|
return "bugfix";
|
|
6865
8568
|
}
|
|
6866
8569
|
return "feature";
|
|
@@ -6889,7 +8592,7 @@ async function readContextFile(projectRoot, filePath, reason) {
|
|
|
6889
8592
|
const relPath = path7.isAbsolute(filePath) ? path7.relative(projectRoot, filePath) : filePath;
|
|
6890
8593
|
return { path: relPath, content, reason, lines };
|
|
6891
8594
|
}
|
|
6892
|
-
function
|
|
8595
|
+
function extractImportSources2(content) {
|
|
6893
8596
|
const sources = [];
|
|
6894
8597
|
const importRegex = /(?:import\s+(?:.*?\s+from\s+)?['"]([^'"]+)['"]|require\(\s*['"]([^'"]+)['"]\s*\))/g;
|
|
6895
8598
|
let match;
|
|
@@ -6931,7 +8634,7 @@ async function gatherImportContext(projectRoot, changedFiles, budget) {
|
|
|
6931
8634
|
const seen = new Set(changedFiles.map((f) => f.path));
|
|
6932
8635
|
for (const cf of changedFiles) {
|
|
6933
8636
|
if (linesGathered >= budget) break;
|
|
6934
|
-
const sources =
|
|
8637
|
+
const sources = extractImportSources2(cf.content);
|
|
6935
8638
|
for (const source of sources) {
|
|
6936
8639
|
if (linesGathered >= budget) break;
|
|
6937
8640
|
const resolved = await resolveImportPath2(projectRoot, cf.path, source);
|
|
@@ -7098,11 +8801,11 @@ async function scopeArchitectureContext(projectRoot, changedFiles, budget, optio
|
|
|
7098
8801
|
return contextFiles;
|
|
7099
8802
|
}
|
|
7100
8803
|
async function scopeContext(options) {
|
|
7101
|
-
const { projectRoot, diff, commitMessage } = options;
|
|
7102
|
-
const changeType = detectChangeType(commitMessage,
|
|
7103
|
-
const budget = computeContextBudget(
|
|
8804
|
+
const { projectRoot, diff: diff2, commitMessage } = options;
|
|
8805
|
+
const changeType = detectChangeType(commitMessage, diff2);
|
|
8806
|
+
const budget = computeContextBudget(diff2.totalDiffLines);
|
|
7104
8807
|
const changedFiles = [];
|
|
7105
|
-
for (const filePath of
|
|
8808
|
+
for (const filePath of diff2.changedFiles) {
|
|
7106
8809
|
const cf = await readContextFile(projectRoot, filePath, "changed");
|
|
7107
8810
|
if (cf) changedFiles.push(cf);
|
|
7108
8811
|
}
|
|
@@ -7122,7 +8825,7 @@ async function scopeContext(options) {
|
|
|
7122
8825
|
changedFiles: [...changedFiles],
|
|
7123
8826
|
contextFiles,
|
|
7124
8827
|
commitHistory: options.commitHistory ?? [],
|
|
7125
|
-
diffLines:
|
|
8828
|
+
diffLines: diff2.totalDiffLines,
|
|
7126
8829
|
contextLines
|
|
7127
8830
|
});
|
|
7128
8831
|
}
|
|
@@ -8132,7 +9835,7 @@ function formatGitHubSummary(options) {
|
|
|
8132
9835
|
async function runReviewPipeline(options) {
|
|
8133
9836
|
const {
|
|
8134
9837
|
projectRoot,
|
|
8135
|
-
diff,
|
|
9838
|
+
diff: diff2,
|
|
8136
9839
|
commitMessage,
|
|
8137
9840
|
flags,
|
|
8138
9841
|
graph,
|
|
@@ -8166,7 +9869,7 @@ async function runReviewPipeline(options) {
|
|
|
8166
9869
|
const mechResult = await runMechanicalChecks({
|
|
8167
9870
|
projectRoot,
|
|
8168
9871
|
config,
|
|
8169
|
-
changedFiles:
|
|
9872
|
+
changedFiles: diff2.changedFiles
|
|
8170
9873
|
});
|
|
8171
9874
|
if (mechResult.ok) {
|
|
8172
9875
|
mechanicalResult = mechResult.value;
|
|
@@ -8205,7 +9908,7 @@ async function runReviewPipeline(options) {
|
|
|
8205
9908
|
try {
|
|
8206
9909
|
contextBundles = await scopeContext({
|
|
8207
9910
|
projectRoot,
|
|
8208
|
-
diff,
|
|
9911
|
+
diff: diff2,
|
|
8209
9912
|
commitMessage,
|
|
8210
9913
|
...graph != null ? { graph } : {},
|
|
8211
9914
|
...conventionFiles != null ? { conventionFiles } : {},
|
|
@@ -8219,14 +9922,14 @@ async function runReviewPipeline(options) {
|
|
|
8219
9922
|
changedFiles: [],
|
|
8220
9923
|
contextFiles: [],
|
|
8221
9924
|
commitHistory: [],
|
|
8222
|
-
diffLines:
|
|
9925
|
+
diffLines: diff2.totalDiffLines,
|
|
8223
9926
|
contextLines: 0
|
|
8224
9927
|
}));
|
|
8225
9928
|
}
|
|
8226
9929
|
const agentResults = await fanOutReview({ bundles: contextBundles });
|
|
8227
9930
|
const rawFindings = agentResults.flatMap((r) => r.findings);
|
|
8228
9931
|
const fileContents = /* @__PURE__ */ new Map();
|
|
8229
|
-
for (const [file, content] of
|
|
9932
|
+
for (const [file, content] of diff2.fileDiffs) {
|
|
8230
9933
|
fileContents.set(file, content);
|
|
8231
9934
|
}
|
|
8232
9935
|
const validatedFindings = await validateFindings({
|
|
@@ -8262,7 +9965,7 @@ async function runReviewPipeline(options) {
|
|
|
8262
9965
|
}
|
|
8263
9966
|
|
|
8264
9967
|
// src/roadmap/parse.ts
|
|
8265
|
-
var
|
|
9968
|
+
var import_types16 = require("@harness-engineering/types");
|
|
8266
9969
|
var VALID_STATUSES = /* @__PURE__ */ new Set([
|
|
8267
9970
|
"backlog",
|
|
8268
9971
|
"planned",
|
|
@@ -8274,14 +9977,14 @@ var EM_DASH = "\u2014";
|
|
|
8274
9977
|
function parseRoadmap(markdown) {
|
|
8275
9978
|
const fmMatch = markdown.match(/^---\n([\s\S]*?)\n---/);
|
|
8276
9979
|
if (!fmMatch) {
|
|
8277
|
-
return (0,
|
|
9980
|
+
return (0, import_types16.Err)(new Error("Missing or malformed YAML frontmatter"));
|
|
8278
9981
|
}
|
|
8279
9982
|
const fmResult = parseFrontmatter(fmMatch[1]);
|
|
8280
9983
|
if (!fmResult.ok) return fmResult;
|
|
8281
9984
|
const body = markdown.slice(fmMatch[0].length);
|
|
8282
9985
|
const milestonesResult = parseMilestones(body);
|
|
8283
9986
|
if (!milestonesResult.ok) return milestonesResult;
|
|
8284
|
-
return (0,
|
|
9987
|
+
return (0, import_types16.Ok)({
|
|
8285
9988
|
frontmatter: fmResult.value,
|
|
8286
9989
|
milestones: milestonesResult.value
|
|
8287
9990
|
});
|
|
@@ -8301,7 +10004,7 @@ function parseFrontmatter(raw) {
|
|
|
8301
10004
|
const lastSynced = map.get("last_synced");
|
|
8302
10005
|
const lastManualEdit = map.get("last_manual_edit");
|
|
8303
10006
|
if (!project || !versionStr || !lastSynced || !lastManualEdit) {
|
|
8304
|
-
return (0,
|
|
10007
|
+
return (0, import_types16.Err)(
|
|
8305
10008
|
new Error(
|
|
8306
10009
|
"Frontmatter missing required fields: project, version, last_synced, last_manual_edit"
|
|
8307
10010
|
)
|
|
@@ -8309,9 +10012,9 @@ function parseFrontmatter(raw) {
|
|
|
8309
10012
|
}
|
|
8310
10013
|
const version = parseInt(versionStr, 10);
|
|
8311
10014
|
if (isNaN(version)) {
|
|
8312
|
-
return (0,
|
|
10015
|
+
return (0, import_types16.Err)(new Error("Frontmatter version must be a number"));
|
|
8313
10016
|
}
|
|
8314
|
-
return (0,
|
|
10017
|
+
return (0, import_types16.Ok)({ project, version, lastSynced, lastManualEdit });
|
|
8315
10018
|
}
|
|
8316
10019
|
function parseMilestones(body) {
|
|
8317
10020
|
const milestones = [];
|
|
@@ -8335,7 +10038,7 @@ function parseMilestones(body) {
|
|
|
8335
10038
|
features: featuresResult.value
|
|
8336
10039
|
});
|
|
8337
10040
|
}
|
|
8338
|
-
return (0,
|
|
10041
|
+
return (0, import_types16.Ok)(milestones);
|
|
8339
10042
|
}
|
|
8340
10043
|
function parseFeatures(sectionBody) {
|
|
8341
10044
|
const features = [];
|
|
@@ -8356,7 +10059,7 @@ function parseFeatures(sectionBody) {
|
|
|
8356
10059
|
if (!featureResult.ok) return featureResult;
|
|
8357
10060
|
features.push(featureResult.value);
|
|
8358
10061
|
}
|
|
8359
|
-
return (0,
|
|
10062
|
+
return (0, import_types16.Ok)(features);
|
|
8360
10063
|
}
|
|
8361
10064
|
function parseFeatureFields(name, body) {
|
|
8362
10065
|
const fieldMap = /* @__PURE__ */ new Map();
|
|
@@ -8367,7 +10070,7 @@ function parseFeatureFields(name, body) {
|
|
|
8367
10070
|
}
|
|
8368
10071
|
const statusRaw = fieldMap.get("Status");
|
|
8369
10072
|
if (!statusRaw || !VALID_STATUSES.has(statusRaw)) {
|
|
8370
|
-
return (0,
|
|
10073
|
+
return (0, import_types16.Err)(
|
|
8371
10074
|
new Error(
|
|
8372
10075
|
`Feature "${name}" has invalid status: "${statusRaw ?? "(missing)"}". Valid statuses: ${[...VALID_STATUSES].join(", ")}`
|
|
8373
10076
|
)
|
|
@@ -8381,7 +10084,7 @@ function parseFeatureFields(name, body) {
|
|
|
8381
10084
|
const blockedByRaw = fieldMap.get("Blocked by") ?? EM_DASH;
|
|
8382
10085
|
const blockedBy = blockedByRaw === EM_DASH ? [] : blockedByRaw.split(",").map((b) => b.trim());
|
|
8383
10086
|
const summary = fieldMap.get("Summary") ?? "";
|
|
8384
|
-
return (0,
|
|
10087
|
+
return (0, import_types16.Ok)({ name, status, spec, plans, blockedBy, summary });
|
|
8385
10088
|
}
|
|
8386
10089
|
|
|
8387
10090
|
// src/roadmap/serialize.ts
|
|
@@ -8425,9 +10128,9 @@ function serializeFeature(feature) {
|
|
|
8425
10128
|
}
|
|
8426
10129
|
|
|
8427
10130
|
// src/roadmap/sync.ts
|
|
8428
|
-
var
|
|
10131
|
+
var fs9 = __toESM(require("fs"));
|
|
8429
10132
|
var path9 = __toESM(require("path"));
|
|
8430
|
-
var
|
|
10133
|
+
var import_types17 = require("@harness-engineering/types");
|
|
8431
10134
|
function inferStatus(feature, projectPath, allFeatures) {
|
|
8432
10135
|
if (feature.blockedBy.length > 0) {
|
|
8433
10136
|
const blockerNotDone = feature.blockedBy.some((blockerName) => {
|
|
@@ -8442,9 +10145,9 @@ function inferStatus(feature, projectPath, allFeatures) {
|
|
|
8442
10145
|
const useRootState = featuresWithPlans.length <= 1;
|
|
8443
10146
|
if (useRootState) {
|
|
8444
10147
|
const rootStatePath = path9.join(projectPath, ".harness", "state.json");
|
|
8445
|
-
if (
|
|
10148
|
+
if (fs9.existsSync(rootStatePath)) {
|
|
8446
10149
|
try {
|
|
8447
|
-
const raw =
|
|
10150
|
+
const raw = fs9.readFileSync(rootStatePath, "utf-8");
|
|
8448
10151
|
const state = JSON.parse(raw);
|
|
8449
10152
|
if (state.progress) {
|
|
8450
10153
|
for (const status of Object.values(state.progress)) {
|
|
@@ -8456,15 +10159,15 @@ function inferStatus(feature, projectPath, allFeatures) {
|
|
|
8456
10159
|
}
|
|
8457
10160
|
}
|
|
8458
10161
|
const sessionsDir = path9.join(projectPath, ".harness", "sessions");
|
|
8459
|
-
if (
|
|
10162
|
+
if (fs9.existsSync(sessionsDir)) {
|
|
8460
10163
|
try {
|
|
8461
|
-
const sessionDirs =
|
|
10164
|
+
const sessionDirs = fs9.readdirSync(sessionsDir, { withFileTypes: true });
|
|
8462
10165
|
for (const entry of sessionDirs) {
|
|
8463
10166
|
if (!entry.isDirectory()) continue;
|
|
8464
10167
|
const autopilotPath = path9.join(sessionsDir, entry.name, "autopilot-state.json");
|
|
8465
|
-
if (!
|
|
10168
|
+
if (!fs9.existsSync(autopilotPath)) continue;
|
|
8466
10169
|
try {
|
|
8467
|
-
const raw =
|
|
10170
|
+
const raw = fs9.readFileSync(autopilotPath, "utf-8");
|
|
8468
10171
|
const autopilot = JSON.parse(raw);
|
|
8469
10172
|
if (!autopilot.phases) continue;
|
|
8470
10173
|
const linkedPhases = autopilot.phases.filter(
|
|
@@ -8511,46 +10214,214 @@ function syncRoadmap(options) {
|
|
|
8511
10214
|
to: inferred
|
|
8512
10215
|
});
|
|
8513
10216
|
}
|
|
8514
|
-
return (0,
|
|
10217
|
+
return (0, import_types17.Ok)(changes);
|
|
8515
10218
|
}
|
|
8516
10219
|
|
|
8517
10220
|
// src/interaction/types.ts
|
|
8518
|
-
var
|
|
8519
|
-
var InteractionTypeSchema =
|
|
8520
|
-
var QuestionSchema =
|
|
8521
|
-
text:
|
|
8522
|
-
options:
|
|
8523
|
-
default:
|
|
10221
|
+
var import_zod7 = require("zod");
|
|
10222
|
+
var InteractionTypeSchema = import_zod7.z.enum(["question", "confirmation", "transition"]);
|
|
10223
|
+
var QuestionSchema = import_zod7.z.object({
|
|
10224
|
+
text: import_zod7.z.string(),
|
|
10225
|
+
options: import_zod7.z.array(import_zod7.z.string()).optional(),
|
|
10226
|
+
default: import_zod7.z.string().optional()
|
|
8524
10227
|
});
|
|
8525
|
-
var ConfirmationSchema =
|
|
8526
|
-
text:
|
|
8527
|
-
context:
|
|
10228
|
+
var ConfirmationSchema = import_zod7.z.object({
|
|
10229
|
+
text: import_zod7.z.string(),
|
|
10230
|
+
context: import_zod7.z.string()
|
|
8528
10231
|
});
|
|
8529
|
-
var TransitionSchema =
|
|
8530
|
-
completedPhase:
|
|
8531
|
-
suggestedNext:
|
|
8532
|
-
reason:
|
|
8533
|
-
artifacts:
|
|
8534
|
-
requiresConfirmation:
|
|
8535
|
-
summary:
|
|
10232
|
+
var TransitionSchema = import_zod7.z.object({
|
|
10233
|
+
completedPhase: import_zod7.z.string(),
|
|
10234
|
+
suggestedNext: import_zod7.z.string(),
|
|
10235
|
+
reason: import_zod7.z.string(),
|
|
10236
|
+
artifacts: import_zod7.z.array(import_zod7.z.string()),
|
|
10237
|
+
requiresConfirmation: import_zod7.z.boolean(),
|
|
10238
|
+
summary: import_zod7.z.string()
|
|
8536
10239
|
});
|
|
8537
|
-
var EmitInteractionInputSchema =
|
|
8538
|
-
path:
|
|
10240
|
+
var EmitInteractionInputSchema = import_zod7.z.object({
|
|
10241
|
+
path: import_zod7.z.string(),
|
|
8539
10242
|
type: InteractionTypeSchema,
|
|
8540
|
-
stream:
|
|
10243
|
+
stream: import_zod7.z.string().optional(),
|
|
8541
10244
|
question: QuestionSchema.optional(),
|
|
8542
10245
|
confirmation: ConfirmationSchema.optional(),
|
|
8543
10246
|
transition: TransitionSchema.optional()
|
|
8544
10247
|
});
|
|
8545
10248
|
|
|
8546
|
-
// src/
|
|
8547
|
-
var
|
|
10249
|
+
// src/blueprint/scanner.ts
|
|
10250
|
+
var fs10 = __toESM(require("fs/promises"));
|
|
8548
10251
|
var path10 = __toESM(require("path"));
|
|
10252
|
+
var ProjectScanner = class {
|
|
10253
|
+
constructor(rootDir) {
|
|
10254
|
+
this.rootDir = rootDir;
|
|
10255
|
+
}
|
|
10256
|
+
async scan() {
|
|
10257
|
+
let projectName = path10.basename(this.rootDir);
|
|
10258
|
+
try {
|
|
10259
|
+
const pkgPath = path10.join(this.rootDir, "package.json");
|
|
10260
|
+
const pkgRaw = await fs10.readFile(pkgPath, "utf-8");
|
|
10261
|
+
const pkg = JSON.parse(pkgRaw);
|
|
10262
|
+
if (pkg.name) projectName = pkg.name;
|
|
10263
|
+
} catch {
|
|
10264
|
+
}
|
|
10265
|
+
return {
|
|
10266
|
+
projectName,
|
|
10267
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
10268
|
+
modules: [
|
|
10269
|
+
{
|
|
10270
|
+
id: "foundations",
|
|
10271
|
+
title: "Foundations",
|
|
10272
|
+
description: "Utility files and basic types.",
|
|
10273
|
+
files: []
|
|
10274
|
+
},
|
|
10275
|
+
{
|
|
10276
|
+
id: "core-logic",
|
|
10277
|
+
title: "Core Logic",
|
|
10278
|
+
description: "Mid-level services and domain logic.",
|
|
10279
|
+
files: []
|
|
10280
|
+
},
|
|
10281
|
+
{
|
|
10282
|
+
id: "interaction-surface",
|
|
10283
|
+
title: "Interaction Surface",
|
|
10284
|
+
description: "APIs, CLIs, and Entry Points.",
|
|
10285
|
+
files: []
|
|
10286
|
+
},
|
|
10287
|
+
{
|
|
10288
|
+
id: "cross-cutting",
|
|
10289
|
+
title: "Cross-Cutting Concerns",
|
|
10290
|
+
description: "Security, Logging, and Observability.",
|
|
10291
|
+
files: []
|
|
10292
|
+
}
|
|
10293
|
+
],
|
|
10294
|
+
hotspots: [],
|
|
10295
|
+
dependencies: []
|
|
10296
|
+
};
|
|
10297
|
+
}
|
|
10298
|
+
};
|
|
10299
|
+
|
|
10300
|
+
// src/blueprint/generator.ts
|
|
10301
|
+
var fs11 = __toESM(require("fs/promises"));
|
|
10302
|
+
var path11 = __toESM(require("path"));
|
|
10303
|
+
var ejs = __toESM(require("ejs"));
|
|
10304
|
+
|
|
10305
|
+
// src/blueprint/templates.ts
|
|
10306
|
+
var SHELL_TEMPLATE = `
|
|
10307
|
+
<!DOCTYPE html>
|
|
10308
|
+
<html lang="en">
|
|
10309
|
+
<head>
|
|
10310
|
+
<meta charset="UTF-8">
|
|
10311
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
10312
|
+
<title>Blueprint: <%= projectName %></title>
|
|
10313
|
+
<style><%- styles %></style>
|
|
10314
|
+
</head>
|
|
10315
|
+
<body>
|
|
10316
|
+
<div id="app">
|
|
10317
|
+
<header>
|
|
10318
|
+
<h1>Blueprint: <%= projectName %></h1>
|
|
10319
|
+
<p>Generated at: <%= generatedAt %></p>
|
|
10320
|
+
</header>
|
|
10321
|
+
<main>
|
|
10322
|
+
<section class="modules">
|
|
10323
|
+
<% modules.forEach(module => { %>
|
|
10324
|
+
<article class="module" id="<%= module.id %>">
|
|
10325
|
+
<h2><%= module.title %></h2>
|
|
10326
|
+
<p><%= module.description %></p>
|
|
10327
|
+
<div class="content">
|
|
10328
|
+
<h3>Code Translation</h3>
|
|
10329
|
+
<div class="translation"><%- module.content.codeTranslation %></div>
|
|
10330
|
+
<h3>Knowledge Check</h3>
|
|
10331
|
+
<div class="quiz">
|
|
10332
|
+
<% module.content.quiz.questions.forEach((q, i) => { %>
|
|
10333
|
+
<div class="question">
|
|
10334
|
+
<p><%= q.question %></p>
|
|
10335
|
+
<button onclick="reveal(this)">Reveal Answer</button>
|
|
10336
|
+
<p class="answer" style="display:none;"><%= q.answer %></p>
|
|
10337
|
+
</div>
|
|
10338
|
+
<% }) %>
|
|
10339
|
+
</div>
|
|
10340
|
+
</div>
|
|
10341
|
+
</article>
|
|
10342
|
+
|
|
10343
|
+
<% }) %>
|
|
10344
|
+
</section>
|
|
10345
|
+
</main>
|
|
10346
|
+
</div>
|
|
10347
|
+
<script><%- scripts %></script>
|
|
10348
|
+
</body>
|
|
10349
|
+
</html>
|
|
10350
|
+
`;
|
|
10351
|
+
var STYLES = `
|
|
10352
|
+
body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; line-height: 1.6; margin: 0; padding: 20px; color: #333; }
|
|
10353
|
+
header { border-bottom: 2px solid #eee; margin-bottom: 20px; padding-bottom: 10px; }
|
|
10354
|
+
.module { background: #f9f9f9; border: 1px solid #ddd; padding: 15px; margin-bottom: 15px; border-radius: 4px; }
|
|
10355
|
+
.module h2 { margin-top: 0; color: #0066cc; }
|
|
10356
|
+
`;
|
|
10357
|
+
var SCRIPTS = `
|
|
10358
|
+
function reveal(btn) {
|
|
10359
|
+
btn.nextElementSibling.style.display = 'block';
|
|
10360
|
+
btn.style.display = 'none';
|
|
10361
|
+
}
|
|
10362
|
+
console.log('Blueprint player initialized.');
|
|
10363
|
+
`;
|
|
10364
|
+
|
|
10365
|
+
// src/shared/llm.ts
|
|
10366
|
+
var MockLLMService = class {
|
|
10367
|
+
async generate(prompt) {
|
|
10368
|
+
return "This is a mock LLM response for: " + prompt;
|
|
10369
|
+
}
|
|
10370
|
+
};
|
|
10371
|
+
var llmService = new MockLLMService();
|
|
10372
|
+
|
|
10373
|
+
// src/blueprint/content-pipeline.ts
|
|
10374
|
+
var ContentPipeline = class {
|
|
10375
|
+
async generateModuleContent(module2) {
|
|
10376
|
+
const codeContext = module2.files.join("\n");
|
|
10377
|
+
const translation = await llmService.generate(
|
|
10378
|
+
`You are a technical educator. Explain the following code clearly and concisely: ${codeContext}`
|
|
10379
|
+
);
|
|
10380
|
+
const quizJson = await llmService.generate(
|
|
10381
|
+
`Create 3 technical quiz questions for this code. Return ONLY valid JSON in this format: { "questions": [{ "question": "...", "answer": "..." }] }. Code: ${codeContext}`
|
|
10382
|
+
);
|
|
10383
|
+
let quiz;
|
|
10384
|
+
try {
|
|
10385
|
+
const cleanJson = quizJson.replace(/```json/g, "").replace(/```/g, "").trim();
|
|
10386
|
+
quiz = JSON.parse(cleanJson);
|
|
10387
|
+
} catch (e) {
|
|
10388
|
+
console.error("Failed to parse quiz JSON", e);
|
|
10389
|
+
quiz = { questions: [{ question: "Failed to generate quiz", answer: "N/A" }] };
|
|
10390
|
+
}
|
|
10391
|
+
return {
|
|
10392
|
+
codeTranslation: translation,
|
|
10393
|
+
quiz
|
|
10394
|
+
};
|
|
10395
|
+
}
|
|
10396
|
+
};
|
|
10397
|
+
|
|
10398
|
+
// src/blueprint/generator.ts
|
|
10399
|
+
var BlueprintGenerator = class {
|
|
10400
|
+
contentPipeline = new ContentPipeline();
|
|
10401
|
+
async generate(data, options) {
|
|
10402
|
+
await Promise.all(
|
|
10403
|
+
data.modules.map(async (module2) => {
|
|
10404
|
+
module2.content = await this.contentPipeline.generateModuleContent(module2);
|
|
10405
|
+
})
|
|
10406
|
+
);
|
|
10407
|
+
const html = ejs.render(SHELL_TEMPLATE, {
|
|
10408
|
+
...data,
|
|
10409
|
+
styles: STYLES,
|
|
10410
|
+
scripts: SCRIPTS
|
|
10411
|
+
});
|
|
10412
|
+
await fs11.mkdir(options.outputDir, { recursive: true });
|
|
10413
|
+
await fs11.writeFile(path11.join(options.outputDir, "index.html"), html);
|
|
10414
|
+
}
|
|
10415
|
+
};
|
|
10416
|
+
|
|
10417
|
+
// src/update-checker.ts
|
|
10418
|
+
var fs12 = __toESM(require("fs"));
|
|
10419
|
+
var path12 = __toESM(require("path"));
|
|
8549
10420
|
var os = __toESM(require("os"));
|
|
8550
10421
|
var import_child_process3 = require("child_process");
|
|
8551
10422
|
function getStatePath() {
|
|
8552
10423
|
const home = process.env["HOME"] || os.homedir();
|
|
8553
|
-
return
|
|
10424
|
+
return path12.join(home, ".harness", "update-check.json");
|
|
8554
10425
|
}
|
|
8555
10426
|
function isUpdateCheckEnabled(configInterval) {
|
|
8556
10427
|
if (process.env["HARNESS_NO_UPDATE_CHECK"] === "1") return false;
|
|
@@ -8563,7 +10434,7 @@ function shouldRunCheck(state, intervalMs) {
|
|
|
8563
10434
|
}
|
|
8564
10435
|
function readCheckState() {
|
|
8565
10436
|
try {
|
|
8566
|
-
const raw =
|
|
10437
|
+
const raw = fs12.readFileSync(getStatePath(), "utf-8");
|
|
8567
10438
|
const parsed = JSON.parse(raw);
|
|
8568
10439
|
if (typeof parsed === "object" && parsed !== null && "lastCheckTime" in parsed && typeof parsed.lastCheckTime === "number" && "currentVersion" in parsed && typeof parsed.currentVersion === "string") {
|
|
8569
10440
|
const state = parsed;
|
|
@@ -8580,7 +10451,7 @@ function readCheckState() {
|
|
|
8580
10451
|
}
|
|
8581
10452
|
function spawnBackgroundCheck(currentVersion) {
|
|
8582
10453
|
const statePath = getStatePath();
|
|
8583
|
-
const stateDir =
|
|
10454
|
+
const stateDir = path12.dirname(statePath);
|
|
8584
10455
|
const script = `
|
|
8585
10456
|
const { execSync } = require('child_process');
|
|
8586
10457
|
const fs = require('fs');
|
|
@@ -8634,39 +10505,64 @@ Run "harness update" to upgrade.`;
|
|
|
8634
10505
|
}
|
|
8635
10506
|
|
|
8636
10507
|
// src/index.ts
|
|
8637
|
-
var VERSION = "
|
|
10508
|
+
var VERSION = "0.11.0";
|
|
8638
10509
|
// Annotate the CommonJS export names for ESM import in node:
|
|
8639
10510
|
0 && (module.exports = {
|
|
8640
10511
|
AGENT_DESCRIPTORS,
|
|
8641
10512
|
ARCHITECTURE_DESCRIPTOR,
|
|
8642
10513
|
AgentActionEmitter,
|
|
10514
|
+
ArchBaselineManager,
|
|
10515
|
+
ArchBaselineSchema,
|
|
10516
|
+
ArchConfigSchema,
|
|
10517
|
+
ArchDiffResultSchema,
|
|
10518
|
+
ArchMetricCategorySchema,
|
|
8643
10519
|
BUG_DETECTION_DESCRIPTOR,
|
|
8644
10520
|
BaselineManager,
|
|
8645
10521
|
BenchmarkRunner,
|
|
10522
|
+
BlueprintGenerator,
|
|
10523
|
+
BundleConstraintsSchema,
|
|
10524
|
+
BundleSchema,
|
|
8646
10525
|
COMPLIANCE_DESCRIPTOR,
|
|
10526
|
+
CategoryBaselineSchema,
|
|
10527
|
+
CategoryRegressionSchema,
|
|
8647
10528
|
ChecklistBuilder,
|
|
10529
|
+
CircularDepsCollector,
|
|
10530
|
+
ComplexityCollector,
|
|
8648
10531
|
ConfirmationSchema,
|
|
8649
10532
|
ConsoleSink,
|
|
10533
|
+
ConstraintRuleSchema,
|
|
10534
|
+
ContentPipeline,
|
|
10535
|
+
ContributionsSchema,
|
|
10536
|
+
CouplingCollector,
|
|
8650
10537
|
CriticalPathResolver,
|
|
8651
10538
|
DEFAULT_PROVIDER_TIERS,
|
|
8652
10539
|
DEFAULT_SECURITY_CONFIG,
|
|
8653
10540
|
DEFAULT_STATE,
|
|
8654
10541
|
DEFAULT_STREAM_INDEX,
|
|
10542
|
+
DepDepthCollector,
|
|
8655
10543
|
EmitInteractionInputSchema,
|
|
8656
10544
|
EntropyAnalyzer,
|
|
8657
10545
|
EntropyConfigSchema,
|
|
8658
10546
|
ExclusionSet,
|
|
8659
10547
|
FailureEntrySchema,
|
|
8660
10548
|
FileSink,
|
|
10549
|
+
ForbiddenImportCollector,
|
|
8661
10550
|
GateConfigSchema,
|
|
8662
10551
|
GateResultSchema,
|
|
8663
10552
|
HandoffSchema,
|
|
8664
10553
|
HarnessStateSchema,
|
|
8665
10554
|
InteractionTypeSchema,
|
|
10555
|
+
LayerViolationCollector,
|
|
10556
|
+
LockfilePackageSchema,
|
|
10557
|
+
LockfileSchema,
|
|
10558
|
+
ManifestSchema,
|
|
10559
|
+
MetricResultSchema,
|
|
10560
|
+
ModuleSizeCollector,
|
|
8666
10561
|
NoOpExecutor,
|
|
8667
10562
|
NoOpSink,
|
|
8668
10563
|
NoOpTelemetryAdapter,
|
|
8669
10564
|
PatternConfigSchema,
|
|
10565
|
+
ProjectScanner,
|
|
8670
10566
|
QuestionSchema,
|
|
8671
10567
|
REQUIRED_SECTIONS,
|
|
8672
10568
|
RegressionDetector,
|
|
@@ -8674,16 +10570,26 @@ var VERSION = "1.8.2";
|
|
|
8674
10570
|
SECURITY_DESCRIPTOR,
|
|
8675
10571
|
SecurityConfigSchema,
|
|
8676
10572
|
SecurityScanner,
|
|
10573
|
+
SharableBoundaryConfigSchema,
|
|
10574
|
+
SharableForbiddenImportSchema,
|
|
10575
|
+
SharableLayerSchema,
|
|
10576
|
+
SharableSecurityRulesSchema,
|
|
8677
10577
|
StreamIndexSchema,
|
|
8678
10578
|
StreamInfoSchema,
|
|
10579
|
+
ThresholdConfigSchema,
|
|
8679
10580
|
TransitionSchema,
|
|
8680
10581
|
TypeScriptParser,
|
|
8681
10582
|
VERSION,
|
|
10583
|
+
ViolationSchema,
|
|
10584
|
+
addProvenance,
|
|
8682
10585
|
analyzeDiff,
|
|
8683
10586
|
appendFailure,
|
|
8684
10587
|
appendLearning,
|
|
8685
10588
|
applyFixes,
|
|
8686
10589
|
applyHotspotDowngrade,
|
|
10590
|
+
archMatchers,
|
|
10591
|
+
archModule,
|
|
10592
|
+
architecture,
|
|
8687
10593
|
archiveFailures,
|
|
8688
10594
|
archiveStream,
|
|
8689
10595
|
buildDependencyGraph,
|
|
@@ -8693,6 +10599,7 @@ var VERSION = "1.8.2";
|
|
|
8693
10599
|
checkEligibility,
|
|
8694
10600
|
classifyFinding,
|
|
8695
10601
|
configureFeedback,
|
|
10602
|
+
constraintRuleId,
|
|
8696
10603
|
contextBudget,
|
|
8697
10604
|
contextFilter,
|
|
8698
10605
|
createBoundaryValidator,
|
|
@@ -8707,6 +10614,8 @@ var VERSION = "1.8.2";
|
|
|
8707
10614
|
cryptoRules,
|
|
8708
10615
|
deduplicateCleanupFindings,
|
|
8709
10616
|
deduplicateFindings,
|
|
10617
|
+
deepMergeConstraints,
|
|
10618
|
+
defaultCollectors,
|
|
8710
10619
|
defineLayer,
|
|
8711
10620
|
deserializationRules,
|
|
8712
10621
|
detectChangeType,
|
|
@@ -8719,9 +10628,12 @@ var VERSION = "1.8.2";
|
|
|
8719
10628
|
detectPatternViolations,
|
|
8720
10629
|
detectSizeBudgetViolations,
|
|
8721
10630
|
detectStack,
|
|
10631
|
+
detectStaleConstraints,
|
|
8722
10632
|
determineAssessment,
|
|
10633
|
+
diff,
|
|
8723
10634
|
executeWorkflow,
|
|
8724
10635
|
expressRules,
|
|
10636
|
+
extractBundle,
|
|
8725
10637
|
extractMarkdownLinks,
|
|
8726
10638
|
extractSections,
|
|
8727
10639
|
fanOutReview,
|
|
@@ -8752,6 +10664,7 @@ var VERSION = "1.8.2";
|
|
|
8752
10664
|
networkRules,
|
|
8753
10665
|
nodeRules,
|
|
8754
10666
|
parseDiff,
|
|
10667
|
+
parseManifest,
|
|
8755
10668
|
parseRoadmap,
|
|
8756
10669
|
parseSecurityConfig,
|
|
8757
10670
|
parseSize,
|
|
@@ -8759,6 +10672,8 @@ var VERSION = "1.8.2";
|
|
|
8759
10672
|
previewFix,
|
|
8760
10673
|
reactRules,
|
|
8761
10674
|
readCheckState,
|
|
10675
|
+
readLockfile,
|
|
10676
|
+
removeProvenance,
|
|
8762
10677
|
requestMultiplePeerReviews,
|
|
8763
10678
|
requestPeerReview,
|
|
8764
10679
|
resetFeedbackConfig,
|
|
@@ -8766,6 +10681,8 @@ var VERSION = "1.8.2";
|
|
|
8766
10681
|
resolveModelTier,
|
|
8767
10682
|
resolveRuleSeverity,
|
|
8768
10683
|
resolveStreamPath,
|
|
10684
|
+
resolveThresholds,
|
|
10685
|
+
runAll,
|
|
8769
10686
|
runArchitectureAgent,
|
|
8770
10687
|
runBugDetectionAgent,
|
|
8771
10688
|
runCIChecks,
|
|
@@ -8785,6 +10702,7 @@ var VERSION = "1.8.2";
|
|
|
8785
10702
|
setActiveStream,
|
|
8786
10703
|
shouldRunCheck,
|
|
8787
10704
|
spawnBackgroundCheck,
|
|
10705
|
+
syncConstraintNodes,
|
|
8788
10706
|
syncRoadmap,
|
|
8789
10707
|
touchStream,
|
|
8790
10708
|
trackAction,
|
|
@@ -8797,6 +10715,9 @@ var VERSION = "1.8.2";
|
|
|
8797
10715
|
validateFindings,
|
|
8798
10716
|
validateKnowledgeMap,
|
|
8799
10717
|
validatePatternConfig,
|
|
10718
|
+
violationId,
|
|
10719
|
+
writeConfig,
|
|
10720
|
+
writeLockfile,
|
|
8800
10721
|
xssRules,
|
|
8801
10722
|
...require("@harness-engineering/types")
|
|
8802
10723
|
});
|