@harness-engineering/core 0.10.0 → 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 +2244 -320
- package/dist/index.mjs +1164 -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,12 +6974,13 @@ 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
|
}
|
|
6983
|
+
learningsCacheMap.delete(learningsPath);
|
|
5330
6984
|
return (0, import_types.Ok)(void 0);
|
|
5331
6985
|
} catch (error) {
|
|
5332
6986
|
return (0, import_types.Err)(
|
|
@@ -5342,17 +6996,17 @@ async function loadRelevantLearnings(projectPath, skillName, stream) {
|
|
|
5342
6996
|
if (!dirResult.ok) return dirResult;
|
|
5343
6997
|
const stateDir = dirResult.value;
|
|
5344
6998
|
const learningsPath = path3.join(stateDir, LEARNINGS_FILE);
|
|
5345
|
-
if (!
|
|
6999
|
+
if (!fs6.existsSync(learningsPath)) {
|
|
5346
7000
|
return (0, import_types.Ok)([]);
|
|
5347
7001
|
}
|
|
5348
|
-
const stats =
|
|
7002
|
+
const stats = fs6.statSync(learningsPath);
|
|
5349
7003
|
const cacheKey = learningsPath;
|
|
5350
7004
|
const cached = learningsCacheMap.get(cacheKey);
|
|
5351
7005
|
let entries;
|
|
5352
7006
|
if (cached && cached.mtimeMs === stats.mtimeMs) {
|
|
5353
7007
|
entries = cached.entries;
|
|
5354
7008
|
} else {
|
|
5355
|
-
const content =
|
|
7009
|
+
const content = fs6.readFileSync(learningsPath, "utf-8");
|
|
5356
7010
|
const lines = content.split("\n");
|
|
5357
7011
|
entries = [];
|
|
5358
7012
|
let currentBlock = [];
|
|
@@ -5395,17 +7049,18 @@ async function appendFailure(projectPath, description, skillName, type, stream)
|
|
|
5395
7049
|
if (!dirResult.ok) return dirResult;
|
|
5396
7050
|
const stateDir = dirResult.value;
|
|
5397
7051
|
const failuresPath = path3.join(stateDir, FAILURES_FILE);
|
|
5398
|
-
|
|
7052
|
+
fs6.mkdirSync(stateDir, { recursive: true });
|
|
5399
7053
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
5400
7054
|
const entry = `
|
|
5401
7055
|
- **${timestamp} [skill:${skillName}] [type:${type}]:** ${description}
|
|
5402
7056
|
`;
|
|
5403
|
-
if (!
|
|
5404
|
-
|
|
7057
|
+
if (!fs6.existsSync(failuresPath)) {
|
|
7058
|
+
fs6.writeFileSync(failuresPath, `# Failures
|
|
5405
7059
|
${entry}`);
|
|
5406
7060
|
} else {
|
|
5407
|
-
|
|
7061
|
+
fs6.appendFileSync(failuresPath, entry);
|
|
5408
7062
|
}
|
|
7063
|
+
failuresCacheMap.delete(failuresPath);
|
|
5409
7064
|
return (0, import_types.Ok)(void 0);
|
|
5410
7065
|
} catch (error) {
|
|
5411
7066
|
return (0, import_types.Err)(
|
|
@@ -5421,16 +7076,16 @@ async function loadFailures(projectPath, stream) {
|
|
|
5421
7076
|
if (!dirResult.ok) return dirResult;
|
|
5422
7077
|
const stateDir = dirResult.value;
|
|
5423
7078
|
const failuresPath = path3.join(stateDir, FAILURES_FILE);
|
|
5424
|
-
if (!
|
|
7079
|
+
if (!fs6.existsSync(failuresPath)) {
|
|
5425
7080
|
return (0, import_types.Ok)([]);
|
|
5426
7081
|
}
|
|
5427
|
-
const stats =
|
|
7082
|
+
const stats = fs6.statSync(failuresPath);
|
|
5428
7083
|
const cacheKey = failuresPath;
|
|
5429
7084
|
const cached = failuresCacheMap.get(cacheKey);
|
|
5430
7085
|
if (cached && cached.mtimeMs === stats.mtimeMs) {
|
|
5431
7086
|
return (0, import_types.Ok)(cached.entries);
|
|
5432
7087
|
}
|
|
5433
|
-
const content =
|
|
7088
|
+
const content = fs6.readFileSync(failuresPath, "utf-8");
|
|
5434
7089
|
const entries = [];
|
|
5435
7090
|
for (const line of content.split("\n")) {
|
|
5436
7091
|
const match = line.match(FAILURE_LINE_REGEX);
|
|
@@ -5460,19 +7115,20 @@ async function archiveFailures(projectPath, stream) {
|
|
|
5460
7115
|
if (!dirResult.ok) return dirResult;
|
|
5461
7116
|
const stateDir = dirResult.value;
|
|
5462
7117
|
const failuresPath = path3.join(stateDir, FAILURES_FILE);
|
|
5463
|
-
if (!
|
|
7118
|
+
if (!fs6.existsSync(failuresPath)) {
|
|
5464
7119
|
return (0, import_types.Ok)(void 0);
|
|
5465
7120
|
}
|
|
5466
7121
|
const archiveDir = path3.join(stateDir, "archive");
|
|
5467
|
-
|
|
7122
|
+
fs6.mkdirSync(archiveDir, { recursive: true });
|
|
5468
7123
|
const date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
5469
7124
|
let archiveName = `failures-${date}.md`;
|
|
5470
7125
|
let counter = 2;
|
|
5471
|
-
while (
|
|
7126
|
+
while (fs6.existsSync(path3.join(archiveDir, archiveName))) {
|
|
5472
7127
|
archiveName = `failures-${date}-${counter}.md`;
|
|
5473
7128
|
counter++;
|
|
5474
7129
|
}
|
|
5475
|
-
|
|
7130
|
+
fs6.renameSync(failuresPath, path3.join(archiveDir, archiveName));
|
|
7131
|
+
failuresCacheMap.delete(failuresPath);
|
|
5476
7132
|
return (0, import_types.Ok)(void 0);
|
|
5477
7133
|
} catch (error) {
|
|
5478
7134
|
return (0, import_types.Err)(
|
|
@@ -5488,8 +7144,8 @@ async function saveHandoff(projectPath, handoff, stream) {
|
|
|
5488
7144
|
if (!dirResult.ok) return dirResult;
|
|
5489
7145
|
const stateDir = dirResult.value;
|
|
5490
7146
|
const handoffPath = path3.join(stateDir, HANDOFF_FILE);
|
|
5491
|
-
|
|
5492
|
-
|
|
7147
|
+
fs6.mkdirSync(stateDir, { recursive: true });
|
|
7148
|
+
fs6.writeFileSync(handoffPath, JSON.stringify(handoff, null, 2));
|
|
5493
7149
|
return (0, import_types.Ok)(void 0);
|
|
5494
7150
|
} catch (error) {
|
|
5495
7151
|
return (0, import_types.Err)(
|
|
@@ -5503,10 +7159,10 @@ async function loadHandoff(projectPath, stream) {
|
|
|
5503
7159
|
if (!dirResult.ok) return dirResult;
|
|
5504
7160
|
const stateDir = dirResult.value;
|
|
5505
7161
|
const handoffPath = path3.join(stateDir, HANDOFF_FILE);
|
|
5506
|
-
if (!
|
|
7162
|
+
if (!fs6.existsSync(handoffPath)) {
|
|
5507
7163
|
return (0, import_types.Ok)(null);
|
|
5508
7164
|
}
|
|
5509
|
-
const raw =
|
|
7165
|
+
const raw = fs6.readFileSync(handoffPath, "utf-8");
|
|
5510
7166
|
const parsed = JSON.parse(raw);
|
|
5511
7167
|
const result = HandoffSchema.safeParse(parsed);
|
|
5512
7168
|
if (!result.success) {
|
|
@@ -5524,8 +7180,8 @@ async function runMechanicalGate(projectPath) {
|
|
|
5524
7180
|
const gateConfigPath = path3.join(harnessDir, GATE_CONFIG_FILE);
|
|
5525
7181
|
try {
|
|
5526
7182
|
let checks = [];
|
|
5527
|
-
if (
|
|
5528
|
-
const raw = JSON.parse(
|
|
7183
|
+
if (fs6.existsSync(gateConfigPath)) {
|
|
7184
|
+
const raw = JSON.parse(fs6.readFileSync(gateConfigPath, "utf-8"));
|
|
5529
7185
|
const config = GateConfigSchema.safeParse(raw);
|
|
5530
7186
|
if (config.success && config.data.checks) {
|
|
5531
7187
|
checks = config.data.checks;
|
|
@@ -5533,19 +7189,19 @@ async function runMechanicalGate(projectPath) {
|
|
|
5533
7189
|
}
|
|
5534
7190
|
if (checks.length === 0) {
|
|
5535
7191
|
const packageJsonPath = path3.join(projectPath, "package.json");
|
|
5536
|
-
if (
|
|
5537
|
-
const pkg = JSON.parse(
|
|
7192
|
+
if (fs6.existsSync(packageJsonPath)) {
|
|
7193
|
+
const pkg = JSON.parse(fs6.readFileSync(packageJsonPath, "utf-8"));
|
|
5538
7194
|
const scripts = pkg.scripts || {};
|
|
5539
7195
|
if (scripts.test) checks.push({ name: "test", command: "npm test" });
|
|
5540
7196
|
if (scripts.lint) checks.push({ name: "lint", command: "npm run lint" });
|
|
5541
7197
|
if (scripts.typecheck) checks.push({ name: "typecheck", command: "npm run typecheck" });
|
|
5542
7198
|
if (scripts.build) checks.push({ name: "build", command: "npm run build" });
|
|
5543
7199
|
}
|
|
5544
|
-
if (
|
|
7200
|
+
if (fs6.existsSync(path3.join(projectPath, "go.mod"))) {
|
|
5545
7201
|
checks.push({ name: "test", command: "go test ./..." });
|
|
5546
7202
|
checks.push({ name: "build", command: "go build ./..." });
|
|
5547
7203
|
}
|
|
5548
|
-
if (
|
|
7204
|
+
if (fs6.existsSync(path3.join(projectPath, "pyproject.toml")) || fs6.existsSync(path3.join(projectPath, "setup.py"))) {
|
|
5549
7205
|
checks.push({ name: "test", command: "python -m pytest" });
|
|
5550
7206
|
}
|
|
5551
7207
|
}
|
|
@@ -5748,7 +7404,7 @@ async function runMultiTurnPipeline(initialContext, turnExecutor, options) {
|
|
|
5748
7404
|
}
|
|
5749
7405
|
|
|
5750
7406
|
// src/security/scanner.ts
|
|
5751
|
-
var
|
|
7407
|
+
var fs8 = __toESM(require("fs/promises"));
|
|
5752
7408
|
|
|
5753
7409
|
// src/security/rules/registry.ts
|
|
5754
7410
|
var RuleRegistry = class {
|
|
@@ -5779,7 +7435,7 @@ var RuleRegistry = class {
|
|
|
5779
7435
|
};
|
|
5780
7436
|
|
|
5781
7437
|
// src/security/config.ts
|
|
5782
|
-
var
|
|
7438
|
+
var import_zod6 = require("zod");
|
|
5783
7439
|
|
|
5784
7440
|
// src/security/types.ts
|
|
5785
7441
|
var DEFAULT_SECURITY_CONFIG = {
|
|
@@ -5790,19 +7446,19 @@ var DEFAULT_SECURITY_CONFIG = {
|
|
|
5790
7446
|
};
|
|
5791
7447
|
|
|
5792
7448
|
// src/security/config.ts
|
|
5793
|
-
var RuleOverrideSchema =
|
|
5794
|
-
var SecurityConfigSchema =
|
|
5795
|
-
enabled:
|
|
5796
|
-
strict:
|
|
5797
|
-
rules:
|
|
5798
|
-
exclude:
|
|
5799
|
-
external:
|
|
5800
|
-
semgrep:
|
|
5801
|
-
enabled:
|
|
5802
|
-
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()
|
|
5803
7459
|
}).optional(),
|
|
5804
|
-
gitleaks:
|
|
5805
|
-
enabled:
|
|
7460
|
+
gitleaks: import_zod6.z.object({
|
|
7461
|
+
enabled: import_zod6.z.union([import_zod6.z.literal("auto"), import_zod6.z.boolean()]).default("auto")
|
|
5806
7462
|
}).optional()
|
|
5807
7463
|
}).optional()
|
|
5808
7464
|
});
|
|
@@ -5835,15 +7491,15 @@ function resolveRuleSeverity(ruleId, defaultSeverity, overrides, strict) {
|
|
|
5835
7491
|
}
|
|
5836
7492
|
|
|
5837
7493
|
// src/security/stack-detector.ts
|
|
5838
|
-
var
|
|
7494
|
+
var fs7 = __toESM(require("fs"));
|
|
5839
7495
|
var path4 = __toESM(require("path"));
|
|
5840
7496
|
function detectStack(projectRoot) {
|
|
5841
7497
|
const stacks = [];
|
|
5842
7498
|
const pkgJsonPath = path4.join(projectRoot, "package.json");
|
|
5843
|
-
if (
|
|
7499
|
+
if (fs7.existsSync(pkgJsonPath)) {
|
|
5844
7500
|
stacks.push("node");
|
|
5845
7501
|
try {
|
|
5846
|
-
const pkgJson = JSON.parse(
|
|
7502
|
+
const pkgJson = JSON.parse(fs7.readFileSync(pkgJsonPath, "utf-8"));
|
|
5847
7503
|
const allDeps = {
|
|
5848
7504
|
...pkgJson.dependencies,
|
|
5849
7505
|
...pkgJson.devDependencies
|
|
@@ -5859,12 +7515,12 @@ function detectStack(projectRoot) {
|
|
|
5859
7515
|
}
|
|
5860
7516
|
}
|
|
5861
7517
|
const goModPath = path4.join(projectRoot, "go.mod");
|
|
5862
|
-
if (
|
|
7518
|
+
if (fs7.existsSync(goModPath)) {
|
|
5863
7519
|
stacks.push("go");
|
|
5864
7520
|
}
|
|
5865
7521
|
const requirementsPath = path4.join(projectRoot, "requirements.txt");
|
|
5866
7522
|
const pyprojectPath = path4.join(projectRoot, "pyproject.toml");
|
|
5867
|
-
if (
|
|
7523
|
+
if (fs7.existsSync(requirementsPath) || fs7.existsSync(pyprojectPath)) {
|
|
5868
7524
|
stacks.push("python");
|
|
5869
7525
|
}
|
|
5870
7526
|
return stacks;
|
|
@@ -6291,7 +7947,7 @@ var SecurityScanner = class {
|
|
|
6291
7947
|
}
|
|
6292
7948
|
async scanFile(filePath) {
|
|
6293
7949
|
if (!this.config.enabled) return [];
|
|
6294
|
-
const content = await
|
|
7950
|
+
const content = await fs8.readFile(filePath, "utf-8");
|
|
6295
7951
|
return this.scanContent(content, filePath, 1);
|
|
6296
7952
|
}
|
|
6297
7953
|
async scanFiles(filePaths) {
|
|
@@ -6324,7 +7980,8 @@ var ALL_CHECKS = [
|
|
|
6324
7980
|
"entropy",
|
|
6325
7981
|
"security",
|
|
6326
7982
|
"perf",
|
|
6327
|
-
"phase-gate"
|
|
7983
|
+
"phase-gate",
|
|
7984
|
+
"arch"
|
|
6328
7985
|
];
|
|
6329
7986
|
async function runSingleCheck(name, projectRoot, config) {
|
|
6330
7987
|
const start = Date.now();
|
|
@@ -6388,7 +8045,17 @@ async function runSingleCheck(name, projectRoot, config) {
|
|
|
6388
8045
|
}
|
|
6389
8046
|
case "docs": {
|
|
6390
8047
|
const docsDir = path5.join(projectRoot, config.docsDir ?? "docs");
|
|
6391
|
-
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
|
+
});
|
|
6392
8059
|
if (!result.ok) {
|
|
6393
8060
|
issues.push({ severity: "warning", message: result.error.message });
|
|
6394
8061
|
} else if (result.value.gaps.length > 0) {
|
|
@@ -6463,11 +8130,13 @@ async function runSingleCheck(name, projectRoot, config) {
|
|
|
6463
8130
|
break;
|
|
6464
8131
|
}
|
|
6465
8132
|
case "perf": {
|
|
8133
|
+
const perfConfig = config.performance || {};
|
|
6466
8134
|
const perfAnalyzer = new EntropyAnalyzer({
|
|
6467
8135
|
rootDir: projectRoot,
|
|
6468
8136
|
analyze: {
|
|
6469
|
-
complexity: true,
|
|
6470
|
-
coupling: true
|
|
8137
|
+
complexity: perfConfig.complexity || true,
|
|
8138
|
+
coupling: perfConfig.coupling || true,
|
|
8139
|
+
sizeBudget: perfConfig.sizeBudget || false
|
|
6471
8140
|
}
|
|
6472
8141
|
});
|
|
6473
8142
|
const perfResult = await perfAnalyzer.analyze();
|
|
@@ -6508,6 +8177,43 @@ async function runSingleCheck(name, projectRoot, config) {
|
|
|
6508
8177
|
});
|
|
6509
8178
|
break;
|
|
6510
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
|
+
}
|
|
6511
8217
|
}
|
|
6512
8218
|
} catch (error) {
|
|
6513
8219
|
issues.push({
|
|
@@ -6842,22 +8548,22 @@ var PREFIX_PATTERNS = [
|
|
|
6842
8548
|
];
|
|
6843
8549
|
var TEST_FILE_PATTERN = /\.(test|spec)\.(ts|tsx|js|jsx|mts|cts)$/;
|
|
6844
8550
|
var MD_FILE_PATTERN = /\.md$/;
|
|
6845
|
-
function detectChangeType(commitMessage,
|
|
8551
|
+
function detectChangeType(commitMessage, diff2) {
|
|
6846
8552
|
const trimmed = commitMessage.trim();
|
|
6847
8553
|
for (const { pattern, type } of PREFIX_PATTERNS) {
|
|
6848
8554
|
if (pattern.test(trimmed)) {
|
|
6849
8555
|
return type;
|
|
6850
8556
|
}
|
|
6851
8557
|
}
|
|
6852
|
-
if (
|
|
8558
|
+
if (diff2.changedFiles.length > 0 && diff2.changedFiles.every((f) => MD_FILE_PATTERN.test(f))) {
|
|
6853
8559
|
return "docs";
|
|
6854
8560
|
}
|
|
6855
|
-
const newNonTestFiles =
|
|
8561
|
+
const newNonTestFiles = diff2.newFiles.filter((f) => !TEST_FILE_PATTERN.test(f));
|
|
6856
8562
|
if (newNonTestFiles.length > 0) {
|
|
6857
8563
|
return "feature";
|
|
6858
8564
|
}
|
|
6859
|
-
const hasNewTestFile =
|
|
6860
|
-
if (
|
|
8565
|
+
const hasNewTestFile = diff2.newFiles.some((f) => TEST_FILE_PATTERN.test(f));
|
|
8566
|
+
if (diff2.totalDiffLines < 20 && hasNewTestFile) {
|
|
6861
8567
|
return "bugfix";
|
|
6862
8568
|
}
|
|
6863
8569
|
return "feature";
|
|
@@ -6886,7 +8592,7 @@ async function readContextFile(projectRoot, filePath, reason) {
|
|
|
6886
8592
|
const relPath = path7.isAbsolute(filePath) ? path7.relative(projectRoot, filePath) : filePath;
|
|
6887
8593
|
return { path: relPath, content, reason, lines };
|
|
6888
8594
|
}
|
|
6889
|
-
function
|
|
8595
|
+
function extractImportSources2(content) {
|
|
6890
8596
|
const sources = [];
|
|
6891
8597
|
const importRegex = /(?:import\s+(?:.*?\s+from\s+)?['"]([^'"]+)['"]|require\(\s*['"]([^'"]+)['"]\s*\))/g;
|
|
6892
8598
|
let match;
|
|
@@ -6928,7 +8634,7 @@ async function gatherImportContext(projectRoot, changedFiles, budget) {
|
|
|
6928
8634
|
const seen = new Set(changedFiles.map((f) => f.path));
|
|
6929
8635
|
for (const cf of changedFiles) {
|
|
6930
8636
|
if (linesGathered >= budget) break;
|
|
6931
|
-
const sources =
|
|
8637
|
+
const sources = extractImportSources2(cf.content);
|
|
6932
8638
|
for (const source of sources) {
|
|
6933
8639
|
if (linesGathered >= budget) break;
|
|
6934
8640
|
const resolved = await resolveImportPath2(projectRoot, cf.path, source);
|
|
@@ -7095,11 +8801,11 @@ async function scopeArchitectureContext(projectRoot, changedFiles, budget, optio
|
|
|
7095
8801
|
return contextFiles;
|
|
7096
8802
|
}
|
|
7097
8803
|
async function scopeContext(options) {
|
|
7098
|
-
const { projectRoot, diff, commitMessage } = options;
|
|
7099
|
-
const changeType = detectChangeType(commitMessage,
|
|
7100
|
-
const budget = computeContextBudget(
|
|
8804
|
+
const { projectRoot, diff: diff2, commitMessage } = options;
|
|
8805
|
+
const changeType = detectChangeType(commitMessage, diff2);
|
|
8806
|
+
const budget = computeContextBudget(diff2.totalDiffLines);
|
|
7101
8807
|
const changedFiles = [];
|
|
7102
|
-
for (const filePath of
|
|
8808
|
+
for (const filePath of diff2.changedFiles) {
|
|
7103
8809
|
const cf = await readContextFile(projectRoot, filePath, "changed");
|
|
7104
8810
|
if (cf) changedFiles.push(cf);
|
|
7105
8811
|
}
|
|
@@ -7119,7 +8825,7 @@ async function scopeContext(options) {
|
|
|
7119
8825
|
changedFiles: [...changedFiles],
|
|
7120
8826
|
contextFiles,
|
|
7121
8827
|
commitHistory: options.commitHistory ?? [],
|
|
7122
|
-
diffLines:
|
|
8828
|
+
diffLines: diff2.totalDiffLines,
|
|
7123
8829
|
contextLines
|
|
7124
8830
|
});
|
|
7125
8831
|
}
|
|
@@ -8129,7 +9835,7 @@ function formatGitHubSummary(options) {
|
|
|
8129
9835
|
async function runReviewPipeline(options) {
|
|
8130
9836
|
const {
|
|
8131
9837
|
projectRoot,
|
|
8132
|
-
diff,
|
|
9838
|
+
diff: diff2,
|
|
8133
9839
|
commitMessage,
|
|
8134
9840
|
flags,
|
|
8135
9841
|
graph,
|
|
@@ -8163,7 +9869,7 @@ async function runReviewPipeline(options) {
|
|
|
8163
9869
|
const mechResult = await runMechanicalChecks({
|
|
8164
9870
|
projectRoot,
|
|
8165
9871
|
config,
|
|
8166
|
-
changedFiles:
|
|
9872
|
+
changedFiles: diff2.changedFiles
|
|
8167
9873
|
});
|
|
8168
9874
|
if (mechResult.ok) {
|
|
8169
9875
|
mechanicalResult = mechResult.value;
|
|
@@ -8202,7 +9908,7 @@ async function runReviewPipeline(options) {
|
|
|
8202
9908
|
try {
|
|
8203
9909
|
contextBundles = await scopeContext({
|
|
8204
9910
|
projectRoot,
|
|
8205
|
-
diff,
|
|
9911
|
+
diff: diff2,
|
|
8206
9912
|
commitMessage,
|
|
8207
9913
|
...graph != null ? { graph } : {},
|
|
8208
9914
|
...conventionFiles != null ? { conventionFiles } : {},
|
|
@@ -8216,14 +9922,14 @@ async function runReviewPipeline(options) {
|
|
|
8216
9922
|
changedFiles: [],
|
|
8217
9923
|
contextFiles: [],
|
|
8218
9924
|
commitHistory: [],
|
|
8219
|
-
diffLines:
|
|
9925
|
+
diffLines: diff2.totalDiffLines,
|
|
8220
9926
|
contextLines: 0
|
|
8221
9927
|
}));
|
|
8222
9928
|
}
|
|
8223
9929
|
const agentResults = await fanOutReview({ bundles: contextBundles });
|
|
8224
9930
|
const rawFindings = agentResults.flatMap((r) => r.findings);
|
|
8225
9931
|
const fileContents = /* @__PURE__ */ new Map();
|
|
8226
|
-
for (const [file, content] of
|
|
9932
|
+
for (const [file, content] of diff2.fileDiffs) {
|
|
8227
9933
|
fileContents.set(file, content);
|
|
8228
9934
|
}
|
|
8229
9935
|
const validatedFindings = await validateFindings({
|
|
@@ -8259,7 +9965,7 @@ async function runReviewPipeline(options) {
|
|
|
8259
9965
|
}
|
|
8260
9966
|
|
|
8261
9967
|
// src/roadmap/parse.ts
|
|
8262
|
-
var
|
|
9968
|
+
var import_types16 = require("@harness-engineering/types");
|
|
8263
9969
|
var VALID_STATUSES = /* @__PURE__ */ new Set([
|
|
8264
9970
|
"backlog",
|
|
8265
9971
|
"planned",
|
|
@@ -8271,14 +9977,14 @@ var EM_DASH = "\u2014";
|
|
|
8271
9977
|
function parseRoadmap(markdown) {
|
|
8272
9978
|
const fmMatch = markdown.match(/^---\n([\s\S]*?)\n---/);
|
|
8273
9979
|
if (!fmMatch) {
|
|
8274
|
-
return (0,
|
|
9980
|
+
return (0, import_types16.Err)(new Error("Missing or malformed YAML frontmatter"));
|
|
8275
9981
|
}
|
|
8276
9982
|
const fmResult = parseFrontmatter(fmMatch[1]);
|
|
8277
9983
|
if (!fmResult.ok) return fmResult;
|
|
8278
9984
|
const body = markdown.slice(fmMatch[0].length);
|
|
8279
9985
|
const milestonesResult = parseMilestones(body);
|
|
8280
9986
|
if (!milestonesResult.ok) return milestonesResult;
|
|
8281
|
-
return (0,
|
|
9987
|
+
return (0, import_types16.Ok)({
|
|
8282
9988
|
frontmatter: fmResult.value,
|
|
8283
9989
|
milestones: milestonesResult.value
|
|
8284
9990
|
});
|
|
@@ -8298,7 +10004,7 @@ function parseFrontmatter(raw) {
|
|
|
8298
10004
|
const lastSynced = map.get("last_synced");
|
|
8299
10005
|
const lastManualEdit = map.get("last_manual_edit");
|
|
8300
10006
|
if (!project || !versionStr || !lastSynced || !lastManualEdit) {
|
|
8301
|
-
return (0,
|
|
10007
|
+
return (0, import_types16.Err)(
|
|
8302
10008
|
new Error(
|
|
8303
10009
|
"Frontmatter missing required fields: project, version, last_synced, last_manual_edit"
|
|
8304
10010
|
)
|
|
@@ -8306,9 +10012,9 @@ function parseFrontmatter(raw) {
|
|
|
8306
10012
|
}
|
|
8307
10013
|
const version = parseInt(versionStr, 10);
|
|
8308
10014
|
if (isNaN(version)) {
|
|
8309
|
-
return (0,
|
|
10015
|
+
return (0, import_types16.Err)(new Error("Frontmatter version must be a number"));
|
|
8310
10016
|
}
|
|
8311
|
-
return (0,
|
|
10017
|
+
return (0, import_types16.Ok)({ project, version, lastSynced, lastManualEdit });
|
|
8312
10018
|
}
|
|
8313
10019
|
function parseMilestones(body) {
|
|
8314
10020
|
const milestones = [];
|
|
@@ -8332,7 +10038,7 @@ function parseMilestones(body) {
|
|
|
8332
10038
|
features: featuresResult.value
|
|
8333
10039
|
});
|
|
8334
10040
|
}
|
|
8335
|
-
return (0,
|
|
10041
|
+
return (0, import_types16.Ok)(milestones);
|
|
8336
10042
|
}
|
|
8337
10043
|
function parseFeatures(sectionBody) {
|
|
8338
10044
|
const features = [];
|
|
@@ -8353,7 +10059,7 @@ function parseFeatures(sectionBody) {
|
|
|
8353
10059
|
if (!featureResult.ok) return featureResult;
|
|
8354
10060
|
features.push(featureResult.value);
|
|
8355
10061
|
}
|
|
8356
|
-
return (0,
|
|
10062
|
+
return (0, import_types16.Ok)(features);
|
|
8357
10063
|
}
|
|
8358
10064
|
function parseFeatureFields(name, body) {
|
|
8359
10065
|
const fieldMap = /* @__PURE__ */ new Map();
|
|
@@ -8364,7 +10070,7 @@ function parseFeatureFields(name, body) {
|
|
|
8364
10070
|
}
|
|
8365
10071
|
const statusRaw = fieldMap.get("Status");
|
|
8366
10072
|
if (!statusRaw || !VALID_STATUSES.has(statusRaw)) {
|
|
8367
|
-
return (0,
|
|
10073
|
+
return (0, import_types16.Err)(
|
|
8368
10074
|
new Error(
|
|
8369
10075
|
`Feature "${name}" has invalid status: "${statusRaw ?? "(missing)"}". Valid statuses: ${[...VALID_STATUSES].join(", ")}`
|
|
8370
10076
|
)
|
|
@@ -8378,7 +10084,7 @@ function parseFeatureFields(name, body) {
|
|
|
8378
10084
|
const blockedByRaw = fieldMap.get("Blocked by") ?? EM_DASH;
|
|
8379
10085
|
const blockedBy = blockedByRaw === EM_DASH ? [] : blockedByRaw.split(",").map((b) => b.trim());
|
|
8380
10086
|
const summary = fieldMap.get("Summary") ?? "";
|
|
8381
|
-
return (0,
|
|
10087
|
+
return (0, import_types16.Ok)({ name, status, spec, plans, blockedBy, summary });
|
|
8382
10088
|
}
|
|
8383
10089
|
|
|
8384
10090
|
// src/roadmap/serialize.ts
|
|
@@ -8422,9 +10128,9 @@ function serializeFeature(feature) {
|
|
|
8422
10128
|
}
|
|
8423
10129
|
|
|
8424
10130
|
// src/roadmap/sync.ts
|
|
8425
|
-
var
|
|
10131
|
+
var fs9 = __toESM(require("fs"));
|
|
8426
10132
|
var path9 = __toESM(require("path"));
|
|
8427
|
-
var
|
|
10133
|
+
var import_types17 = require("@harness-engineering/types");
|
|
8428
10134
|
function inferStatus(feature, projectPath, allFeatures) {
|
|
8429
10135
|
if (feature.blockedBy.length > 0) {
|
|
8430
10136
|
const blockerNotDone = feature.blockedBy.some((blockerName) => {
|
|
@@ -8439,9 +10145,9 @@ function inferStatus(feature, projectPath, allFeatures) {
|
|
|
8439
10145
|
const useRootState = featuresWithPlans.length <= 1;
|
|
8440
10146
|
if (useRootState) {
|
|
8441
10147
|
const rootStatePath = path9.join(projectPath, ".harness", "state.json");
|
|
8442
|
-
if (
|
|
10148
|
+
if (fs9.existsSync(rootStatePath)) {
|
|
8443
10149
|
try {
|
|
8444
|
-
const raw =
|
|
10150
|
+
const raw = fs9.readFileSync(rootStatePath, "utf-8");
|
|
8445
10151
|
const state = JSON.parse(raw);
|
|
8446
10152
|
if (state.progress) {
|
|
8447
10153
|
for (const status of Object.values(state.progress)) {
|
|
@@ -8453,15 +10159,15 @@ function inferStatus(feature, projectPath, allFeatures) {
|
|
|
8453
10159
|
}
|
|
8454
10160
|
}
|
|
8455
10161
|
const sessionsDir = path9.join(projectPath, ".harness", "sessions");
|
|
8456
|
-
if (
|
|
10162
|
+
if (fs9.existsSync(sessionsDir)) {
|
|
8457
10163
|
try {
|
|
8458
|
-
const sessionDirs =
|
|
10164
|
+
const sessionDirs = fs9.readdirSync(sessionsDir, { withFileTypes: true });
|
|
8459
10165
|
for (const entry of sessionDirs) {
|
|
8460
10166
|
if (!entry.isDirectory()) continue;
|
|
8461
10167
|
const autopilotPath = path9.join(sessionsDir, entry.name, "autopilot-state.json");
|
|
8462
|
-
if (!
|
|
10168
|
+
if (!fs9.existsSync(autopilotPath)) continue;
|
|
8463
10169
|
try {
|
|
8464
|
-
const raw =
|
|
10170
|
+
const raw = fs9.readFileSync(autopilotPath, "utf-8");
|
|
8465
10171
|
const autopilot = JSON.parse(raw);
|
|
8466
10172
|
if (!autopilot.phases) continue;
|
|
8467
10173
|
const linkedPhases = autopilot.phases.filter(
|
|
@@ -8508,46 +10214,214 @@ function syncRoadmap(options) {
|
|
|
8508
10214
|
to: inferred
|
|
8509
10215
|
});
|
|
8510
10216
|
}
|
|
8511
|
-
return (0,
|
|
10217
|
+
return (0, import_types17.Ok)(changes);
|
|
8512
10218
|
}
|
|
8513
10219
|
|
|
8514
10220
|
// src/interaction/types.ts
|
|
8515
|
-
var
|
|
8516
|
-
var InteractionTypeSchema =
|
|
8517
|
-
var QuestionSchema =
|
|
8518
|
-
text:
|
|
8519
|
-
options:
|
|
8520
|
-
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()
|
|
8521
10227
|
});
|
|
8522
|
-
var ConfirmationSchema =
|
|
8523
|
-
text:
|
|
8524
|
-
context:
|
|
10228
|
+
var ConfirmationSchema = import_zod7.z.object({
|
|
10229
|
+
text: import_zod7.z.string(),
|
|
10230
|
+
context: import_zod7.z.string()
|
|
8525
10231
|
});
|
|
8526
|
-
var TransitionSchema =
|
|
8527
|
-
completedPhase:
|
|
8528
|
-
suggestedNext:
|
|
8529
|
-
reason:
|
|
8530
|
-
artifacts:
|
|
8531
|
-
requiresConfirmation:
|
|
8532
|
-
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()
|
|
8533
10239
|
});
|
|
8534
|
-
var EmitInteractionInputSchema =
|
|
8535
|
-
path:
|
|
10240
|
+
var EmitInteractionInputSchema = import_zod7.z.object({
|
|
10241
|
+
path: import_zod7.z.string(),
|
|
8536
10242
|
type: InteractionTypeSchema,
|
|
8537
|
-
stream:
|
|
10243
|
+
stream: import_zod7.z.string().optional(),
|
|
8538
10244
|
question: QuestionSchema.optional(),
|
|
8539
10245
|
confirmation: ConfirmationSchema.optional(),
|
|
8540
10246
|
transition: TransitionSchema.optional()
|
|
8541
10247
|
});
|
|
8542
10248
|
|
|
8543
|
-
// src/
|
|
8544
|
-
var
|
|
10249
|
+
// src/blueprint/scanner.ts
|
|
10250
|
+
var fs10 = __toESM(require("fs/promises"));
|
|
8545
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"));
|
|
8546
10420
|
var os = __toESM(require("os"));
|
|
8547
10421
|
var import_child_process3 = require("child_process");
|
|
8548
10422
|
function getStatePath() {
|
|
8549
10423
|
const home = process.env["HOME"] || os.homedir();
|
|
8550
|
-
return
|
|
10424
|
+
return path12.join(home, ".harness", "update-check.json");
|
|
8551
10425
|
}
|
|
8552
10426
|
function isUpdateCheckEnabled(configInterval) {
|
|
8553
10427
|
if (process.env["HARNESS_NO_UPDATE_CHECK"] === "1") return false;
|
|
@@ -8560,7 +10434,7 @@ function shouldRunCheck(state, intervalMs) {
|
|
|
8560
10434
|
}
|
|
8561
10435
|
function readCheckState() {
|
|
8562
10436
|
try {
|
|
8563
|
-
const raw =
|
|
10437
|
+
const raw = fs12.readFileSync(getStatePath(), "utf-8");
|
|
8564
10438
|
const parsed = JSON.parse(raw);
|
|
8565
10439
|
if (typeof parsed === "object" && parsed !== null && "lastCheckTime" in parsed && typeof parsed.lastCheckTime === "number" && "currentVersion" in parsed && typeof parsed.currentVersion === "string") {
|
|
8566
10440
|
const state = parsed;
|
|
@@ -8577,7 +10451,7 @@ function readCheckState() {
|
|
|
8577
10451
|
}
|
|
8578
10452
|
function spawnBackgroundCheck(currentVersion) {
|
|
8579
10453
|
const statePath = getStatePath();
|
|
8580
|
-
const stateDir =
|
|
10454
|
+
const stateDir = path12.dirname(statePath);
|
|
8581
10455
|
const script = `
|
|
8582
10456
|
const { execSync } = require('child_process');
|
|
8583
10457
|
const fs = require('fs');
|
|
@@ -8631,39 +10505,64 @@ Run "harness update" to upgrade.`;
|
|
|
8631
10505
|
}
|
|
8632
10506
|
|
|
8633
10507
|
// src/index.ts
|
|
8634
|
-
var VERSION = "
|
|
10508
|
+
var VERSION = "0.11.0";
|
|
8635
10509
|
// Annotate the CommonJS export names for ESM import in node:
|
|
8636
10510
|
0 && (module.exports = {
|
|
8637
10511
|
AGENT_DESCRIPTORS,
|
|
8638
10512
|
ARCHITECTURE_DESCRIPTOR,
|
|
8639
10513
|
AgentActionEmitter,
|
|
10514
|
+
ArchBaselineManager,
|
|
10515
|
+
ArchBaselineSchema,
|
|
10516
|
+
ArchConfigSchema,
|
|
10517
|
+
ArchDiffResultSchema,
|
|
10518
|
+
ArchMetricCategorySchema,
|
|
8640
10519
|
BUG_DETECTION_DESCRIPTOR,
|
|
8641
10520
|
BaselineManager,
|
|
8642
10521
|
BenchmarkRunner,
|
|
10522
|
+
BlueprintGenerator,
|
|
10523
|
+
BundleConstraintsSchema,
|
|
10524
|
+
BundleSchema,
|
|
8643
10525
|
COMPLIANCE_DESCRIPTOR,
|
|
10526
|
+
CategoryBaselineSchema,
|
|
10527
|
+
CategoryRegressionSchema,
|
|
8644
10528
|
ChecklistBuilder,
|
|
10529
|
+
CircularDepsCollector,
|
|
10530
|
+
ComplexityCollector,
|
|
8645
10531
|
ConfirmationSchema,
|
|
8646
10532
|
ConsoleSink,
|
|
10533
|
+
ConstraintRuleSchema,
|
|
10534
|
+
ContentPipeline,
|
|
10535
|
+
ContributionsSchema,
|
|
10536
|
+
CouplingCollector,
|
|
8647
10537
|
CriticalPathResolver,
|
|
8648
10538
|
DEFAULT_PROVIDER_TIERS,
|
|
8649
10539
|
DEFAULT_SECURITY_CONFIG,
|
|
8650
10540
|
DEFAULT_STATE,
|
|
8651
10541
|
DEFAULT_STREAM_INDEX,
|
|
10542
|
+
DepDepthCollector,
|
|
8652
10543
|
EmitInteractionInputSchema,
|
|
8653
10544
|
EntropyAnalyzer,
|
|
8654
10545
|
EntropyConfigSchema,
|
|
8655
10546
|
ExclusionSet,
|
|
8656
10547
|
FailureEntrySchema,
|
|
8657
10548
|
FileSink,
|
|
10549
|
+
ForbiddenImportCollector,
|
|
8658
10550
|
GateConfigSchema,
|
|
8659
10551
|
GateResultSchema,
|
|
8660
10552
|
HandoffSchema,
|
|
8661
10553
|
HarnessStateSchema,
|
|
8662
10554
|
InteractionTypeSchema,
|
|
10555
|
+
LayerViolationCollector,
|
|
10556
|
+
LockfilePackageSchema,
|
|
10557
|
+
LockfileSchema,
|
|
10558
|
+
ManifestSchema,
|
|
10559
|
+
MetricResultSchema,
|
|
10560
|
+
ModuleSizeCollector,
|
|
8663
10561
|
NoOpExecutor,
|
|
8664
10562
|
NoOpSink,
|
|
8665
10563
|
NoOpTelemetryAdapter,
|
|
8666
10564
|
PatternConfigSchema,
|
|
10565
|
+
ProjectScanner,
|
|
8667
10566
|
QuestionSchema,
|
|
8668
10567
|
REQUIRED_SECTIONS,
|
|
8669
10568
|
RegressionDetector,
|
|
@@ -8671,16 +10570,26 @@ var VERSION = "1.8.2";
|
|
|
8671
10570
|
SECURITY_DESCRIPTOR,
|
|
8672
10571
|
SecurityConfigSchema,
|
|
8673
10572
|
SecurityScanner,
|
|
10573
|
+
SharableBoundaryConfigSchema,
|
|
10574
|
+
SharableForbiddenImportSchema,
|
|
10575
|
+
SharableLayerSchema,
|
|
10576
|
+
SharableSecurityRulesSchema,
|
|
8674
10577
|
StreamIndexSchema,
|
|
8675
10578
|
StreamInfoSchema,
|
|
10579
|
+
ThresholdConfigSchema,
|
|
8676
10580
|
TransitionSchema,
|
|
8677
10581
|
TypeScriptParser,
|
|
8678
10582
|
VERSION,
|
|
10583
|
+
ViolationSchema,
|
|
10584
|
+
addProvenance,
|
|
8679
10585
|
analyzeDiff,
|
|
8680
10586
|
appendFailure,
|
|
8681
10587
|
appendLearning,
|
|
8682
10588
|
applyFixes,
|
|
8683
10589
|
applyHotspotDowngrade,
|
|
10590
|
+
archMatchers,
|
|
10591
|
+
archModule,
|
|
10592
|
+
architecture,
|
|
8684
10593
|
archiveFailures,
|
|
8685
10594
|
archiveStream,
|
|
8686
10595
|
buildDependencyGraph,
|
|
@@ -8690,6 +10599,7 @@ var VERSION = "1.8.2";
|
|
|
8690
10599
|
checkEligibility,
|
|
8691
10600
|
classifyFinding,
|
|
8692
10601
|
configureFeedback,
|
|
10602
|
+
constraintRuleId,
|
|
8693
10603
|
contextBudget,
|
|
8694
10604
|
contextFilter,
|
|
8695
10605
|
createBoundaryValidator,
|
|
@@ -8704,6 +10614,8 @@ var VERSION = "1.8.2";
|
|
|
8704
10614
|
cryptoRules,
|
|
8705
10615
|
deduplicateCleanupFindings,
|
|
8706
10616
|
deduplicateFindings,
|
|
10617
|
+
deepMergeConstraints,
|
|
10618
|
+
defaultCollectors,
|
|
8707
10619
|
defineLayer,
|
|
8708
10620
|
deserializationRules,
|
|
8709
10621
|
detectChangeType,
|
|
@@ -8716,9 +10628,12 @@ var VERSION = "1.8.2";
|
|
|
8716
10628
|
detectPatternViolations,
|
|
8717
10629
|
detectSizeBudgetViolations,
|
|
8718
10630
|
detectStack,
|
|
10631
|
+
detectStaleConstraints,
|
|
8719
10632
|
determineAssessment,
|
|
10633
|
+
diff,
|
|
8720
10634
|
executeWorkflow,
|
|
8721
10635
|
expressRules,
|
|
10636
|
+
extractBundle,
|
|
8722
10637
|
extractMarkdownLinks,
|
|
8723
10638
|
extractSections,
|
|
8724
10639
|
fanOutReview,
|
|
@@ -8749,6 +10664,7 @@ var VERSION = "1.8.2";
|
|
|
8749
10664
|
networkRules,
|
|
8750
10665
|
nodeRules,
|
|
8751
10666
|
parseDiff,
|
|
10667
|
+
parseManifest,
|
|
8752
10668
|
parseRoadmap,
|
|
8753
10669
|
parseSecurityConfig,
|
|
8754
10670
|
parseSize,
|
|
@@ -8756,6 +10672,8 @@ var VERSION = "1.8.2";
|
|
|
8756
10672
|
previewFix,
|
|
8757
10673
|
reactRules,
|
|
8758
10674
|
readCheckState,
|
|
10675
|
+
readLockfile,
|
|
10676
|
+
removeProvenance,
|
|
8759
10677
|
requestMultiplePeerReviews,
|
|
8760
10678
|
requestPeerReview,
|
|
8761
10679
|
resetFeedbackConfig,
|
|
@@ -8763,6 +10681,8 @@ var VERSION = "1.8.2";
|
|
|
8763
10681
|
resolveModelTier,
|
|
8764
10682
|
resolveRuleSeverity,
|
|
8765
10683
|
resolveStreamPath,
|
|
10684
|
+
resolveThresholds,
|
|
10685
|
+
runAll,
|
|
8766
10686
|
runArchitectureAgent,
|
|
8767
10687
|
runBugDetectionAgent,
|
|
8768
10688
|
runCIChecks,
|
|
@@ -8782,6 +10702,7 @@ var VERSION = "1.8.2";
|
|
|
8782
10702
|
setActiveStream,
|
|
8783
10703
|
shouldRunCheck,
|
|
8784
10704
|
spawnBackgroundCheck,
|
|
10705
|
+
syncConstraintNodes,
|
|
8785
10706
|
syncRoadmap,
|
|
8786
10707
|
touchStream,
|
|
8787
10708
|
trackAction,
|
|
@@ -8794,6 +10715,9 @@ var VERSION = "1.8.2";
|
|
|
8794
10715
|
validateFindings,
|
|
8795
10716
|
validateKnowledgeMap,
|
|
8796
10717
|
validatePatternConfig,
|
|
10718
|
+
violationId,
|
|
10719
|
+
writeConfig,
|
|
10720
|
+
writeLockfile,
|
|
8797
10721
|
xssRules,
|
|
8798
10722
|
...require("@harness-engineering/types")
|
|
8799
10723
|
});
|