@harness-engineering/core 0.10.1 → 0.12.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 +885 -23
- package/dist/index.d.ts +885 -23
- package/dist/index.js +2286 -320
- package/dist/index.mjs +1205 -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,9 @@ __export(index_exports, {
|
|
|
153
195
|
previewFix: () => previewFix,
|
|
154
196
|
reactRules: () => reactRules,
|
|
155
197
|
readCheckState: () => readCheckState,
|
|
198
|
+
readLockfile: () => readLockfile,
|
|
199
|
+
removeContributions: () => removeContributions,
|
|
200
|
+
removeProvenance: () => removeProvenance,
|
|
156
201
|
requestMultiplePeerReviews: () => requestMultiplePeerReviews,
|
|
157
202
|
requestPeerReview: () => requestPeerReview,
|
|
158
203
|
resetFeedbackConfig: () => resetFeedbackConfig,
|
|
@@ -160,6 +205,8 @@ __export(index_exports, {
|
|
|
160
205
|
resolveModelTier: () => resolveModelTier,
|
|
161
206
|
resolveRuleSeverity: () => resolveRuleSeverity,
|
|
162
207
|
resolveStreamPath: () => resolveStreamPath,
|
|
208
|
+
resolveThresholds: () => resolveThresholds,
|
|
209
|
+
runAll: () => runAll,
|
|
163
210
|
runArchitectureAgent: () => runArchitectureAgent,
|
|
164
211
|
runBugDetectionAgent: () => runBugDetectionAgent,
|
|
165
212
|
runCIChecks: () => runCIChecks,
|
|
@@ -179,6 +226,7 @@ __export(index_exports, {
|
|
|
179
226
|
setActiveStream: () => setActiveStream,
|
|
180
227
|
shouldRunCheck: () => shouldRunCheck,
|
|
181
228
|
spawnBackgroundCheck: () => spawnBackgroundCheck,
|
|
229
|
+
syncConstraintNodes: () => syncConstraintNodes,
|
|
182
230
|
syncRoadmap: () => syncRoadmap,
|
|
183
231
|
touchStream: () => touchStream,
|
|
184
232
|
trackAction: () => trackAction,
|
|
@@ -191,6 +239,9 @@ __export(index_exports, {
|
|
|
191
239
|
validateFindings: () => validateFindings,
|
|
192
240
|
validateKnowledgeMap: () => validateKnowledgeMap,
|
|
193
241
|
validatePatternConfig: () => validatePatternConfig,
|
|
242
|
+
violationId: () => violationId,
|
|
243
|
+
writeConfig: () => writeConfig,
|
|
244
|
+
writeLockfile: () => writeLockfile,
|
|
194
245
|
xssRules: () => xssRules
|
|
195
246
|
});
|
|
196
247
|
module.exports = __toCommonJS(index_exports);
|
|
@@ -213,17 +264,17 @@ var import_util = require("util");
|
|
|
213
264
|
var import_glob = require("glob");
|
|
214
265
|
var accessAsync = (0, import_util.promisify)(import_fs.access);
|
|
215
266
|
var readFileAsync = (0, import_util.promisify)(import_fs.readFile);
|
|
216
|
-
async function fileExists(
|
|
267
|
+
async function fileExists(path13) {
|
|
217
268
|
try {
|
|
218
|
-
await accessAsync(
|
|
269
|
+
await accessAsync(path13, import_fs.constants.F_OK);
|
|
219
270
|
return true;
|
|
220
271
|
} catch {
|
|
221
272
|
return false;
|
|
222
273
|
}
|
|
223
274
|
}
|
|
224
|
-
async function readFileContent(
|
|
275
|
+
async function readFileContent(path13) {
|
|
225
276
|
try {
|
|
226
|
-
const content = await readFileAsync(
|
|
277
|
+
const content = await readFileAsync(path13, "utf-8");
|
|
227
278
|
return (0, import_types.Ok)(content);
|
|
228
279
|
} catch (error) {
|
|
229
280
|
return (0, import_types.Err)(error);
|
|
@@ -271,15 +322,15 @@ function validateConfig(data, schema) {
|
|
|
271
322
|
let message = "Configuration validation failed";
|
|
272
323
|
const suggestions = [];
|
|
273
324
|
if (firstError) {
|
|
274
|
-
const
|
|
275
|
-
const pathDisplay =
|
|
325
|
+
const path13 = firstError.path.join(".");
|
|
326
|
+
const pathDisplay = path13 ? ` at "${path13}"` : "";
|
|
276
327
|
if (firstError.code === "invalid_type") {
|
|
277
328
|
const received = firstError.received;
|
|
278
329
|
const expected = firstError.expected;
|
|
279
330
|
if (received === "undefined") {
|
|
280
331
|
code = "MISSING_FIELD";
|
|
281
332
|
message = `Missing required field${pathDisplay}: ${firstError.message}`;
|
|
282
|
-
suggestions.push(`Field "${
|
|
333
|
+
suggestions.push(`Field "${path13}" is required and must be of type "${expected}"`);
|
|
283
334
|
} else {
|
|
284
335
|
code = "INVALID_TYPE";
|
|
285
336
|
message = `Invalid type${pathDisplay}: ${firstError.message}`;
|
|
@@ -492,30 +543,30 @@ function extractSections(content) {
|
|
|
492
543
|
return result;
|
|
493
544
|
});
|
|
494
545
|
}
|
|
495
|
-
function isExternalLink(
|
|
496
|
-
return
|
|
546
|
+
function isExternalLink(path13) {
|
|
547
|
+
return path13.startsWith("http://") || path13.startsWith("https://") || path13.startsWith("#") || path13.startsWith("mailto:");
|
|
497
548
|
}
|
|
498
549
|
function resolveLinkPath(linkPath, baseDir) {
|
|
499
550
|
return linkPath.startsWith(".") ? (0, import_path.join)(baseDir, linkPath) : linkPath;
|
|
500
551
|
}
|
|
501
|
-
async function validateAgentsMap(
|
|
552
|
+
async function validateAgentsMap(path13 = "./AGENTS.md") {
|
|
502
553
|
console.warn(
|
|
503
554
|
"[harness] validateAgentsMap() is deprecated. Use graph-based validation via Assembler.checkCoverage() from @harness-engineering/graph"
|
|
504
555
|
);
|
|
505
|
-
const contentResult = await readFileContent(
|
|
556
|
+
const contentResult = await readFileContent(path13);
|
|
506
557
|
if (!contentResult.ok) {
|
|
507
558
|
return (0, import_types.Err)(
|
|
508
559
|
createError(
|
|
509
560
|
"PARSE_ERROR",
|
|
510
561
|
`Failed to read AGENTS.md: ${contentResult.error.message}`,
|
|
511
|
-
{ path:
|
|
562
|
+
{ path: path13 },
|
|
512
563
|
["Ensure the file exists", "Check file permissions"]
|
|
513
564
|
)
|
|
514
565
|
);
|
|
515
566
|
}
|
|
516
567
|
const content = contentResult.value;
|
|
517
568
|
const sections = extractSections(content);
|
|
518
|
-
const baseDir = (0, import_path.dirname)(
|
|
569
|
+
const baseDir = (0, import_path.dirname)(path13);
|
|
519
570
|
const sectionTitles = sections.map((s) => s.title);
|
|
520
571
|
const missingSections = REQUIRED_SECTIONS.filter(
|
|
521
572
|
(required) => !sectionTitles.some((title) => title.toLowerCase().includes(required.toLowerCase()))
|
|
@@ -555,6 +606,7 @@ async function validateAgentsMap(path11 = "./AGENTS.md") {
|
|
|
555
606
|
}
|
|
556
607
|
|
|
557
608
|
// src/context/doc-coverage.ts
|
|
609
|
+
var import_minimatch = require("minimatch");
|
|
558
610
|
var import_path2 = require("path");
|
|
559
611
|
function determineImportance(filePath) {
|
|
560
612
|
const name = (0, import_path2.basename)(filePath).toLowerCase();
|
|
@@ -577,7 +629,7 @@ function suggestSection(filePath, domain) {
|
|
|
577
629
|
return `${domain} Reference`;
|
|
578
630
|
}
|
|
579
631
|
async function checkDocCoverage(domain, options = {}) {
|
|
580
|
-
const { docsDir = "./docs", sourceDir = "
|
|
632
|
+
const { docsDir = "./docs", sourceDir = ".", excludePatterns = [], graphCoverage } = options;
|
|
581
633
|
if (graphCoverage) {
|
|
582
634
|
const gaps = graphCoverage.undocumented.map((file) => ({
|
|
583
635
|
file,
|
|
@@ -597,9 +649,7 @@ async function checkDocCoverage(domain, options = {}) {
|
|
|
597
649
|
const filteredSourceFiles = sourceFiles.filter((file) => {
|
|
598
650
|
const relativePath = (0, import_path2.relative)(sourceDir, file);
|
|
599
651
|
return !excludePatterns.some((pattern) => {
|
|
600
|
-
|
|
601
|
-
const regex = new RegExp("^" + escaped + "$");
|
|
602
|
-
return regex.test(relativePath) || regex.test(file);
|
|
652
|
+
return (0, import_minimatch.minimatch)(relativePath, pattern, { dot: true }) || (0, import_minimatch.minimatch)(file, pattern, { dot: true });
|
|
603
653
|
});
|
|
604
654
|
});
|
|
605
655
|
const docFiles = await findFiles("**/*.md", docsDir);
|
|
@@ -657,8 +707,8 @@ async function checkDocCoverage(domain, options = {}) {
|
|
|
657
707
|
|
|
658
708
|
// src/context/knowledge-map.ts
|
|
659
709
|
var import_path3 = require("path");
|
|
660
|
-
function suggestFix(
|
|
661
|
-
const targetName = (0, import_path3.basename)(
|
|
710
|
+
function suggestFix(path13, existingFiles) {
|
|
711
|
+
const targetName = (0, import_path3.basename)(path13).toLowerCase();
|
|
662
712
|
const similar = existingFiles.find((file) => {
|
|
663
713
|
const fileName = (0, import_path3.basename)(file).toLowerCase();
|
|
664
714
|
return fileName.includes(targetName) || targetName.includes(fileName);
|
|
@@ -666,7 +716,7 @@ function suggestFix(path11, existingFiles) {
|
|
|
666
716
|
if (similar) {
|
|
667
717
|
return `Did you mean "${similar}"?`;
|
|
668
718
|
}
|
|
669
|
-
return `Create the file "${
|
|
719
|
+
return `Create the file "${path13}" or remove the link`;
|
|
670
720
|
}
|
|
671
721
|
async function validateKnowledgeMap(rootDir = process.cwd()) {
|
|
672
722
|
console.warn(
|
|
@@ -1011,7 +1061,7 @@ function getPhaseCategories(phase) {
|
|
|
1011
1061
|
}
|
|
1012
1062
|
|
|
1013
1063
|
// src/constraints/layers.ts
|
|
1014
|
-
var
|
|
1064
|
+
var import_minimatch2 = require("minimatch");
|
|
1015
1065
|
function defineLayer(name, patterns, allowedDependencies) {
|
|
1016
1066
|
return {
|
|
1017
1067
|
name,
|
|
@@ -1022,7 +1072,7 @@ function defineLayer(name, patterns, allowedDependencies) {
|
|
|
1022
1072
|
function resolveFileToLayer(file, layers) {
|
|
1023
1073
|
for (const layer of layers) {
|
|
1024
1074
|
for (const pattern of layer.patterns) {
|
|
1025
|
-
if ((0,
|
|
1075
|
+
if ((0, import_minimatch2.minimatch)(file, pattern)) {
|
|
1026
1076
|
return layer;
|
|
1027
1077
|
}
|
|
1028
1078
|
}
|
|
@@ -1269,8 +1319,8 @@ function createBoundaryValidator(schema, name) {
|
|
|
1269
1319
|
return (0, import_types.Ok)(result.data);
|
|
1270
1320
|
}
|
|
1271
1321
|
const suggestions = result.error.issues.map((issue) => {
|
|
1272
|
-
const
|
|
1273
|
-
return
|
|
1322
|
+
const path13 = issue.path.join(".");
|
|
1323
|
+
return path13 ? `${path13}: ${issue.message}` : issue.message;
|
|
1274
1324
|
});
|
|
1275
1325
|
return (0, import_types.Err)(
|
|
1276
1326
|
createError(
|
|
@@ -1314,6 +1364,491 @@ function validateBoundaries(boundaries, data) {
|
|
|
1314
1364
|
});
|
|
1315
1365
|
}
|
|
1316
1366
|
|
|
1367
|
+
// src/constraints/sharing/types.ts
|
|
1368
|
+
var import_zod = require("zod");
|
|
1369
|
+
var ManifestSchema = import_zod.z.object({
|
|
1370
|
+
name: import_zod.z.string(),
|
|
1371
|
+
version: import_zod.z.string(),
|
|
1372
|
+
description: import_zod.z.string().optional(),
|
|
1373
|
+
include: import_zod.z.array(import_zod.z.string()).min(1),
|
|
1374
|
+
minHarnessVersion: import_zod.z.string().optional(),
|
|
1375
|
+
keywords: import_zod.z.array(import_zod.z.string()).optional().default([]),
|
|
1376
|
+
layers: import_zod.z.record(import_zod.z.array(import_zod.z.string())).optional(),
|
|
1377
|
+
boundaries: import_zod.z.array(
|
|
1378
|
+
import_zod.z.object({
|
|
1379
|
+
name: import_zod.z.string(),
|
|
1380
|
+
layer: import_zod.z.string(),
|
|
1381
|
+
direction: import_zod.z.enum(["input", "output"]),
|
|
1382
|
+
schema: import_zod.z.string()
|
|
1383
|
+
})
|
|
1384
|
+
).optional()
|
|
1385
|
+
});
|
|
1386
|
+
var BundleConstraintsSchema = import_zod.z.object({
|
|
1387
|
+
layers: import_zod.z.array(
|
|
1388
|
+
import_zod.z.object({
|
|
1389
|
+
name: import_zod.z.string(),
|
|
1390
|
+
pattern: import_zod.z.string(),
|
|
1391
|
+
allowedDependencies: import_zod.z.array(import_zod.z.string())
|
|
1392
|
+
})
|
|
1393
|
+
).optional(),
|
|
1394
|
+
forbiddenImports: import_zod.z.array(
|
|
1395
|
+
import_zod.z.object({
|
|
1396
|
+
from: import_zod.z.string(),
|
|
1397
|
+
disallow: import_zod.z.array(import_zod.z.string()),
|
|
1398
|
+
message: import_zod.z.string().optional()
|
|
1399
|
+
})
|
|
1400
|
+
).optional(),
|
|
1401
|
+
boundaries: import_zod.z.object({
|
|
1402
|
+
requireSchema: import_zod.z.array(import_zod.z.string()).optional()
|
|
1403
|
+
}).optional(),
|
|
1404
|
+
architecture: import_zod.z.object({
|
|
1405
|
+
thresholds: import_zod.z.record(import_zod.z.unknown()).optional(),
|
|
1406
|
+
modules: import_zod.z.record(import_zod.z.record(import_zod.z.unknown())).optional()
|
|
1407
|
+
}).optional(),
|
|
1408
|
+
security: import_zod.z.object({
|
|
1409
|
+
rules: import_zod.z.record(import_zod.z.string()).optional()
|
|
1410
|
+
}).optional()
|
|
1411
|
+
});
|
|
1412
|
+
var BundleSchema = import_zod.z.object({
|
|
1413
|
+
name: import_zod.z.string(),
|
|
1414
|
+
version: import_zod.z.string(),
|
|
1415
|
+
description: import_zod.z.string().optional(),
|
|
1416
|
+
minHarnessVersion: import_zod.z.string().optional(),
|
|
1417
|
+
manifest: ManifestSchema,
|
|
1418
|
+
constraints: BundleConstraintsSchema,
|
|
1419
|
+
contributions: import_zod.z.record(import_zod.z.unknown()).optional()
|
|
1420
|
+
});
|
|
1421
|
+
var ContributionsSchema = import_zod.z.record(import_zod.z.unknown());
|
|
1422
|
+
var LockfilePackageSchema = import_zod.z.object({
|
|
1423
|
+
version: import_zod.z.string(),
|
|
1424
|
+
source: import_zod.z.string(),
|
|
1425
|
+
installedAt: import_zod.z.string(),
|
|
1426
|
+
contributions: import_zod.z.record(import_zod.z.unknown()).optional().nullable(),
|
|
1427
|
+
resolved: import_zod.z.string().optional(),
|
|
1428
|
+
integrity: import_zod.z.string().optional(),
|
|
1429
|
+
provenance: import_zod.z.array(import_zod.z.string()).optional()
|
|
1430
|
+
});
|
|
1431
|
+
var LockfileSchema = import_zod.z.object({
|
|
1432
|
+
version: import_zod.z.literal(1).optional(),
|
|
1433
|
+
// Used by some tests
|
|
1434
|
+
lockfileVersion: import_zod.z.literal(1).optional(),
|
|
1435
|
+
// Standard field
|
|
1436
|
+
packages: import_zod.z.record(LockfilePackageSchema)
|
|
1437
|
+
}).refine((data) => data.version !== void 0 || data.lockfileVersion !== void 0, {
|
|
1438
|
+
message: "Either 'version' or 'lockfileVersion' must be present and equal to 1"
|
|
1439
|
+
});
|
|
1440
|
+
var SharableLayerSchema = import_zod.z.unknown();
|
|
1441
|
+
var SharableForbiddenImportSchema = import_zod.z.unknown();
|
|
1442
|
+
var SharableBoundaryConfigSchema = import_zod.z.unknown();
|
|
1443
|
+
var SharableSecurityRulesSchema = import_zod.z.unknown();
|
|
1444
|
+
|
|
1445
|
+
// src/constraints/sharing/write-config.ts
|
|
1446
|
+
var fs = __toESM(require("fs/promises"));
|
|
1447
|
+
async function writeConfig(filePath, content) {
|
|
1448
|
+
try {
|
|
1449
|
+
const json = JSON.stringify(content, null, 2) + "\n";
|
|
1450
|
+
await fs.writeFile(filePath, json, "utf-8");
|
|
1451
|
+
return (0, import_types.Ok)(void 0);
|
|
1452
|
+
} catch (error) {
|
|
1453
|
+
return (0, import_types.Err)(error instanceof Error ? error : new Error(String(error)));
|
|
1454
|
+
}
|
|
1455
|
+
}
|
|
1456
|
+
|
|
1457
|
+
// src/constraints/sharing/manifest.ts
|
|
1458
|
+
function parseManifest(parsed) {
|
|
1459
|
+
const result = ManifestSchema.safeParse(parsed);
|
|
1460
|
+
if (!result.success) {
|
|
1461
|
+
const issues = result.error.issues.map((i) => `${i.path.join(".") || "(root)"}: ${i.message}`).join("; ");
|
|
1462
|
+
return { ok: false, error: `Invalid manifest: ${issues}` };
|
|
1463
|
+
}
|
|
1464
|
+
return { ok: true, value: result.data };
|
|
1465
|
+
}
|
|
1466
|
+
|
|
1467
|
+
// src/constraints/sharing/bundle.ts
|
|
1468
|
+
function resolveDotPath(obj, dotPath) {
|
|
1469
|
+
const segments = dotPath.split(".");
|
|
1470
|
+
let current = obj;
|
|
1471
|
+
for (const segment of segments) {
|
|
1472
|
+
if (current === null || typeof current !== "object") {
|
|
1473
|
+
return void 0;
|
|
1474
|
+
}
|
|
1475
|
+
current = current[segment];
|
|
1476
|
+
}
|
|
1477
|
+
return current;
|
|
1478
|
+
}
|
|
1479
|
+
function setDotPath(obj, dotPath, value) {
|
|
1480
|
+
const segments = dotPath.split(".");
|
|
1481
|
+
const lastSegment = segments[segments.length - 1];
|
|
1482
|
+
const parentSegments = segments.slice(0, -1);
|
|
1483
|
+
let current = obj;
|
|
1484
|
+
for (const segment of parentSegments) {
|
|
1485
|
+
if (current[segment] === void 0 || current[segment] === null || typeof current[segment] !== "object") {
|
|
1486
|
+
current[segment] = {};
|
|
1487
|
+
}
|
|
1488
|
+
current = current[segment];
|
|
1489
|
+
}
|
|
1490
|
+
if (lastSegment !== void 0) {
|
|
1491
|
+
current[lastSegment] = value;
|
|
1492
|
+
}
|
|
1493
|
+
}
|
|
1494
|
+
function extractBundle(manifest, config) {
|
|
1495
|
+
const constraints = {};
|
|
1496
|
+
for (const includePath of manifest.include) {
|
|
1497
|
+
const value = resolveDotPath(config, includePath);
|
|
1498
|
+
if (value !== void 0) {
|
|
1499
|
+
setDotPath(constraints, includePath, value);
|
|
1500
|
+
}
|
|
1501
|
+
}
|
|
1502
|
+
const bundle = {
|
|
1503
|
+
name: manifest.name,
|
|
1504
|
+
version: manifest.version,
|
|
1505
|
+
...manifest.minHarnessVersion !== void 0 && {
|
|
1506
|
+
minHarnessVersion: manifest.minHarnessVersion
|
|
1507
|
+
},
|
|
1508
|
+
...manifest.description !== void 0 && {
|
|
1509
|
+
description: manifest.description
|
|
1510
|
+
},
|
|
1511
|
+
manifest,
|
|
1512
|
+
constraints
|
|
1513
|
+
};
|
|
1514
|
+
const parsed = BundleSchema.safeParse(bundle);
|
|
1515
|
+
if (!parsed.success) {
|
|
1516
|
+
const issues = parsed.error.issues.map((i) => `${i.path.join(".") || "(root)"}: ${i.message}`).join("; ");
|
|
1517
|
+
return { ok: false, error: `Invalid bundle: ${issues}` };
|
|
1518
|
+
}
|
|
1519
|
+
return { ok: true, value: parsed.data };
|
|
1520
|
+
}
|
|
1521
|
+
|
|
1522
|
+
// src/constraints/sharing/merge.ts
|
|
1523
|
+
function deepEqual(a, b) {
|
|
1524
|
+
if (a === b) return true;
|
|
1525
|
+
if (typeof a !== typeof b) return false;
|
|
1526
|
+
if (typeof a !== "object" || a === null || b === null) return false;
|
|
1527
|
+
if (Array.isArray(a) && Array.isArray(b)) {
|
|
1528
|
+
if (a.length !== b.length) return false;
|
|
1529
|
+
return a.every((val, i) => deepEqual(val, b[i]));
|
|
1530
|
+
}
|
|
1531
|
+
if (Array.isArray(a) !== Array.isArray(b)) return false;
|
|
1532
|
+
const keysA = Object.keys(a);
|
|
1533
|
+
const keysB = Object.keys(b);
|
|
1534
|
+
if (keysA.length !== keysB.length) return false;
|
|
1535
|
+
return keysA.every(
|
|
1536
|
+
(key) => deepEqual(a[key], b[key])
|
|
1537
|
+
);
|
|
1538
|
+
}
|
|
1539
|
+
function stringArraysEqual(a, b) {
|
|
1540
|
+
if (a.length !== b.length) return false;
|
|
1541
|
+
const sortedA = [...a].sort();
|
|
1542
|
+
const sortedB = [...b].sort();
|
|
1543
|
+
return sortedA.every((val, i) => val === sortedB[i]);
|
|
1544
|
+
}
|
|
1545
|
+
function deepMergeConstraints(localConfig, bundleConstraints, _existingContributions) {
|
|
1546
|
+
const config = { ...localConfig };
|
|
1547
|
+
const contributions = {};
|
|
1548
|
+
const conflicts = [];
|
|
1549
|
+
if (bundleConstraints.layers && bundleConstraints.layers.length > 0) {
|
|
1550
|
+
const localLayers = Array.isArray(localConfig.layers) ? localConfig.layers : [];
|
|
1551
|
+
const mergedLayers = [...localLayers];
|
|
1552
|
+
const contributedLayerNames = [];
|
|
1553
|
+
for (const bundleLayer of bundleConstraints.layers) {
|
|
1554
|
+
const existing = localLayers.find((l) => l.name === bundleLayer.name);
|
|
1555
|
+
if (!existing) {
|
|
1556
|
+
mergedLayers.push(bundleLayer);
|
|
1557
|
+
contributedLayerNames.push(bundleLayer.name);
|
|
1558
|
+
} else {
|
|
1559
|
+
const same = existing.pattern === bundleLayer.pattern && stringArraysEqual(existing.allowedDependencies, bundleLayer.allowedDependencies);
|
|
1560
|
+
if (!same) {
|
|
1561
|
+
conflicts.push({
|
|
1562
|
+
section: "layers",
|
|
1563
|
+
key: bundleLayer.name,
|
|
1564
|
+
localValue: existing,
|
|
1565
|
+
packageValue: bundleLayer,
|
|
1566
|
+
description: `Layer '${bundleLayer.name}' already exists locally with different configuration`
|
|
1567
|
+
});
|
|
1568
|
+
}
|
|
1569
|
+
}
|
|
1570
|
+
}
|
|
1571
|
+
config.layers = mergedLayers;
|
|
1572
|
+
if (contributedLayerNames.length > 0) {
|
|
1573
|
+
contributions.layers = contributedLayerNames;
|
|
1574
|
+
}
|
|
1575
|
+
}
|
|
1576
|
+
if (bundleConstraints.forbiddenImports && bundleConstraints.forbiddenImports.length > 0) {
|
|
1577
|
+
const localFI = Array.isArray(localConfig.forbiddenImports) ? localConfig.forbiddenImports : [];
|
|
1578
|
+
const mergedFI = [...localFI];
|
|
1579
|
+
const contributedFromKeys = [];
|
|
1580
|
+
for (const bundleRule of bundleConstraints.forbiddenImports) {
|
|
1581
|
+
const existing = localFI.find((r) => r.from === bundleRule.from);
|
|
1582
|
+
if (!existing) {
|
|
1583
|
+
const entry = {
|
|
1584
|
+
from: bundleRule.from,
|
|
1585
|
+
disallow: bundleRule.disallow
|
|
1586
|
+
};
|
|
1587
|
+
if (bundleRule.message !== void 0) {
|
|
1588
|
+
entry.message = bundleRule.message;
|
|
1589
|
+
}
|
|
1590
|
+
mergedFI.push(entry);
|
|
1591
|
+
contributedFromKeys.push(bundleRule.from);
|
|
1592
|
+
} else {
|
|
1593
|
+
const same = stringArraysEqual(existing.disallow, bundleRule.disallow);
|
|
1594
|
+
if (!same) {
|
|
1595
|
+
conflicts.push({
|
|
1596
|
+
section: "forbiddenImports",
|
|
1597
|
+
key: bundleRule.from,
|
|
1598
|
+
localValue: existing,
|
|
1599
|
+
packageValue: bundleRule,
|
|
1600
|
+
description: `Forbidden import rule for '${bundleRule.from}' already exists locally with different disallow list`
|
|
1601
|
+
});
|
|
1602
|
+
}
|
|
1603
|
+
}
|
|
1604
|
+
}
|
|
1605
|
+
config.forbiddenImports = mergedFI;
|
|
1606
|
+
if (contributedFromKeys.length > 0) {
|
|
1607
|
+
contributions.forbiddenImports = contributedFromKeys;
|
|
1608
|
+
}
|
|
1609
|
+
}
|
|
1610
|
+
if (bundleConstraints.boundaries) {
|
|
1611
|
+
const localBoundaries = localConfig.boundaries ?? { requireSchema: [] };
|
|
1612
|
+
const localSchemas = new Set(localBoundaries.requireSchema ?? []);
|
|
1613
|
+
const bundleSchemas = bundleConstraints.boundaries.requireSchema ?? [];
|
|
1614
|
+
const newSchemas = [];
|
|
1615
|
+
for (const schema of bundleSchemas) {
|
|
1616
|
+
if (!localSchemas.has(schema)) {
|
|
1617
|
+
newSchemas.push(schema);
|
|
1618
|
+
localSchemas.add(schema);
|
|
1619
|
+
}
|
|
1620
|
+
}
|
|
1621
|
+
config.boundaries = {
|
|
1622
|
+
requireSchema: [...localBoundaries.requireSchema ?? [], ...newSchemas]
|
|
1623
|
+
};
|
|
1624
|
+
if (newSchemas.length > 0) {
|
|
1625
|
+
contributions.boundaries = newSchemas;
|
|
1626
|
+
}
|
|
1627
|
+
}
|
|
1628
|
+
if (bundleConstraints.architecture) {
|
|
1629
|
+
const localArch = localConfig.architecture ?? {
|
|
1630
|
+
thresholds: {},
|
|
1631
|
+
modules: {}
|
|
1632
|
+
};
|
|
1633
|
+
const mergedThresholds = { ...localArch.thresholds };
|
|
1634
|
+
const contributedThresholdKeys = [];
|
|
1635
|
+
const bundleThresholds = bundleConstraints.architecture.thresholds ?? {};
|
|
1636
|
+
for (const [category, value] of Object.entries(bundleThresholds)) {
|
|
1637
|
+
if (!(category in mergedThresholds)) {
|
|
1638
|
+
mergedThresholds[category] = value;
|
|
1639
|
+
contributedThresholdKeys.push(category);
|
|
1640
|
+
} else if (!deepEqual(mergedThresholds[category], value)) {
|
|
1641
|
+
conflicts.push({
|
|
1642
|
+
section: "architecture.thresholds",
|
|
1643
|
+
key: category,
|
|
1644
|
+
localValue: mergedThresholds[category],
|
|
1645
|
+
packageValue: value,
|
|
1646
|
+
description: `Architecture threshold '${category}' already exists locally with a different value`
|
|
1647
|
+
});
|
|
1648
|
+
}
|
|
1649
|
+
}
|
|
1650
|
+
const mergedModules = { ...localArch.modules };
|
|
1651
|
+
const contributedModuleKeys = [];
|
|
1652
|
+
const bundleModules = bundleConstraints.architecture.modules ?? {};
|
|
1653
|
+
for (const [modulePath, bundleCategoryMap] of Object.entries(bundleModules)) {
|
|
1654
|
+
if (!(modulePath in mergedModules)) {
|
|
1655
|
+
mergedModules[modulePath] = bundleCategoryMap;
|
|
1656
|
+
for (const cat of Object.keys(bundleCategoryMap)) {
|
|
1657
|
+
contributedModuleKeys.push(`${modulePath}:${cat}`);
|
|
1658
|
+
}
|
|
1659
|
+
} else {
|
|
1660
|
+
const localCategoryMap = mergedModules[modulePath];
|
|
1661
|
+
const mergedCategoryMap = { ...localCategoryMap };
|
|
1662
|
+
for (const [category, value] of Object.entries(bundleCategoryMap)) {
|
|
1663
|
+
if (!(category in mergedCategoryMap)) {
|
|
1664
|
+
mergedCategoryMap[category] = value;
|
|
1665
|
+
contributedModuleKeys.push(`${modulePath}:${category}`);
|
|
1666
|
+
} else if (!deepEqual(mergedCategoryMap[category], value)) {
|
|
1667
|
+
conflicts.push({
|
|
1668
|
+
section: "architecture.modules",
|
|
1669
|
+
key: `${modulePath}:${category}`,
|
|
1670
|
+
localValue: mergedCategoryMap[category],
|
|
1671
|
+
packageValue: value,
|
|
1672
|
+
description: `Architecture module override '${modulePath}' category '${category}' already exists locally with a different value`
|
|
1673
|
+
});
|
|
1674
|
+
}
|
|
1675
|
+
}
|
|
1676
|
+
mergedModules[modulePath] = mergedCategoryMap;
|
|
1677
|
+
}
|
|
1678
|
+
}
|
|
1679
|
+
config.architecture = {
|
|
1680
|
+
...localArch,
|
|
1681
|
+
thresholds: mergedThresholds,
|
|
1682
|
+
modules: mergedModules
|
|
1683
|
+
};
|
|
1684
|
+
if (contributedThresholdKeys.length > 0) {
|
|
1685
|
+
contributions["architecture.thresholds"] = contributedThresholdKeys;
|
|
1686
|
+
}
|
|
1687
|
+
if (contributedModuleKeys.length > 0) {
|
|
1688
|
+
contributions["architecture.modules"] = contributedModuleKeys;
|
|
1689
|
+
}
|
|
1690
|
+
}
|
|
1691
|
+
if (bundleConstraints.security?.rules) {
|
|
1692
|
+
const localSecurity = localConfig.security ?? { rules: {} };
|
|
1693
|
+
const localRules = localSecurity.rules ?? {};
|
|
1694
|
+
const mergedRules = { ...localRules };
|
|
1695
|
+
const contributedRuleIds = [];
|
|
1696
|
+
for (const [ruleId, severity] of Object.entries(bundleConstraints.security.rules)) {
|
|
1697
|
+
if (!(ruleId in mergedRules)) {
|
|
1698
|
+
mergedRules[ruleId] = severity;
|
|
1699
|
+
contributedRuleIds.push(ruleId);
|
|
1700
|
+
} else if (mergedRules[ruleId] !== severity) {
|
|
1701
|
+
conflicts.push({
|
|
1702
|
+
section: "security.rules",
|
|
1703
|
+
key: ruleId,
|
|
1704
|
+
localValue: mergedRules[ruleId],
|
|
1705
|
+
packageValue: severity,
|
|
1706
|
+
description: `Security rule '${ruleId}' already exists locally with severity '${mergedRules[ruleId]}', bundle has '${severity}'`
|
|
1707
|
+
});
|
|
1708
|
+
}
|
|
1709
|
+
}
|
|
1710
|
+
config.security = { ...localSecurity, rules: mergedRules };
|
|
1711
|
+
if (contributedRuleIds.length > 0) {
|
|
1712
|
+
contributions["security.rules"] = contributedRuleIds;
|
|
1713
|
+
}
|
|
1714
|
+
}
|
|
1715
|
+
return { config, contributions, conflicts };
|
|
1716
|
+
}
|
|
1717
|
+
|
|
1718
|
+
// src/constraints/sharing/lockfile.ts
|
|
1719
|
+
var fs2 = __toESM(require("fs/promises"));
|
|
1720
|
+
async function readLockfile(lockfilePath) {
|
|
1721
|
+
let raw;
|
|
1722
|
+
try {
|
|
1723
|
+
raw = await fs2.readFile(lockfilePath, "utf-8");
|
|
1724
|
+
} catch (err) {
|
|
1725
|
+
if (isNodeError(err) && err.code === "ENOENT") {
|
|
1726
|
+
return { ok: true, value: null };
|
|
1727
|
+
}
|
|
1728
|
+
return {
|
|
1729
|
+
ok: false,
|
|
1730
|
+
error: `Failed to read lockfile: ${err instanceof Error ? err.message : String(err)}`
|
|
1731
|
+
};
|
|
1732
|
+
}
|
|
1733
|
+
let parsed;
|
|
1734
|
+
try {
|
|
1735
|
+
parsed = JSON.parse(raw);
|
|
1736
|
+
} catch {
|
|
1737
|
+
return {
|
|
1738
|
+
ok: false,
|
|
1739
|
+
error: `Failed to parse lockfile as JSON: file contains invalid JSON`
|
|
1740
|
+
};
|
|
1741
|
+
}
|
|
1742
|
+
const result = LockfileSchema.safeParse(parsed);
|
|
1743
|
+
if (!result.success) {
|
|
1744
|
+
return {
|
|
1745
|
+
ok: false,
|
|
1746
|
+
error: `Lockfile schema validation failed: ${result.error.issues.map((i) => i.message).join(", ")}`
|
|
1747
|
+
};
|
|
1748
|
+
}
|
|
1749
|
+
return { ok: true, value: result.data };
|
|
1750
|
+
}
|
|
1751
|
+
async function writeLockfile(lockfilePath, lockfile) {
|
|
1752
|
+
return writeConfig(lockfilePath, lockfile);
|
|
1753
|
+
}
|
|
1754
|
+
function addProvenance(lockfile, packageName, entry) {
|
|
1755
|
+
return {
|
|
1756
|
+
...lockfile,
|
|
1757
|
+
packages: {
|
|
1758
|
+
...lockfile.packages,
|
|
1759
|
+
[packageName]: entry
|
|
1760
|
+
}
|
|
1761
|
+
};
|
|
1762
|
+
}
|
|
1763
|
+
function removeProvenance(lockfile, packageName) {
|
|
1764
|
+
const existing = lockfile.packages[packageName];
|
|
1765
|
+
if (!existing) {
|
|
1766
|
+
return { lockfile, contributions: null };
|
|
1767
|
+
}
|
|
1768
|
+
const { [packageName]: _removed, ...remaining } = lockfile.packages;
|
|
1769
|
+
return {
|
|
1770
|
+
lockfile: {
|
|
1771
|
+
...lockfile,
|
|
1772
|
+
packages: remaining
|
|
1773
|
+
},
|
|
1774
|
+
contributions: existing.contributions ?? null
|
|
1775
|
+
};
|
|
1776
|
+
}
|
|
1777
|
+
function isNodeError(err) {
|
|
1778
|
+
return err instanceof Error && "code" in err;
|
|
1779
|
+
}
|
|
1780
|
+
|
|
1781
|
+
// src/constraints/sharing/remove.ts
|
|
1782
|
+
function removeContributions(config, contributions) {
|
|
1783
|
+
const result = { ...config };
|
|
1784
|
+
const layerNames = contributions.layers;
|
|
1785
|
+
if (layerNames && layerNames.length > 0 && Array.isArray(result.layers)) {
|
|
1786
|
+
const nameSet = new Set(layerNames);
|
|
1787
|
+
result.layers = result.layers.filter((l) => !nameSet.has(l.name));
|
|
1788
|
+
}
|
|
1789
|
+
const fromKeys = contributions.forbiddenImports;
|
|
1790
|
+
if (fromKeys && fromKeys.length > 0 && Array.isArray(result.forbiddenImports)) {
|
|
1791
|
+
const fromSet = new Set(fromKeys);
|
|
1792
|
+
result.forbiddenImports = result.forbiddenImports.filter(
|
|
1793
|
+
(r) => !fromSet.has(r.from)
|
|
1794
|
+
);
|
|
1795
|
+
}
|
|
1796
|
+
const boundarySchemas = contributions.boundaries;
|
|
1797
|
+
if (boundarySchemas && boundarySchemas.length > 0 && result.boundaries) {
|
|
1798
|
+
const boundaries = result.boundaries;
|
|
1799
|
+
if (boundaries.requireSchema) {
|
|
1800
|
+
const schemaSet = new Set(boundarySchemas);
|
|
1801
|
+
result.boundaries = {
|
|
1802
|
+
...boundaries,
|
|
1803
|
+
requireSchema: boundaries.requireSchema.filter((s) => !schemaSet.has(s))
|
|
1804
|
+
};
|
|
1805
|
+
}
|
|
1806
|
+
}
|
|
1807
|
+
const thresholdKeys = contributions["architecture.thresholds"];
|
|
1808
|
+
if (thresholdKeys && thresholdKeys.length > 0 && result.architecture) {
|
|
1809
|
+
const arch = { ...result.architecture };
|
|
1810
|
+
const thresholds = { ...arch.thresholds };
|
|
1811
|
+
for (const key of thresholdKeys) {
|
|
1812
|
+
delete thresholds[key];
|
|
1813
|
+
}
|
|
1814
|
+
arch.thresholds = thresholds;
|
|
1815
|
+
result.architecture = arch;
|
|
1816
|
+
}
|
|
1817
|
+
const moduleKeys = contributions["architecture.modules"];
|
|
1818
|
+
if (moduleKeys && moduleKeys.length > 0 && result.architecture) {
|
|
1819
|
+
const arch = { ...result.architecture };
|
|
1820
|
+
const modules = { ...arch.modules };
|
|
1821
|
+
for (const key of moduleKeys) {
|
|
1822
|
+
const colonIdx = key.indexOf(":");
|
|
1823
|
+
if (colonIdx === -1) continue;
|
|
1824
|
+
const modulePath = key.substring(0, colonIdx);
|
|
1825
|
+
const category = key.substring(colonIdx + 1);
|
|
1826
|
+
if (modules[modulePath]) {
|
|
1827
|
+
const moduleCategories = { ...modules[modulePath] };
|
|
1828
|
+
delete moduleCategories[category];
|
|
1829
|
+
if (Object.keys(moduleCategories).length === 0) {
|
|
1830
|
+
delete modules[modulePath];
|
|
1831
|
+
} else {
|
|
1832
|
+
modules[modulePath] = moduleCategories;
|
|
1833
|
+
}
|
|
1834
|
+
}
|
|
1835
|
+
}
|
|
1836
|
+
arch.modules = modules;
|
|
1837
|
+
result.architecture = arch;
|
|
1838
|
+
}
|
|
1839
|
+
const ruleIds = contributions["security.rules"];
|
|
1840
|
+
if (ruleIds && ruleIds.length > 0 && result.security) {
|
|
1841
|
+
const security = { ...result.security };
|
|
1842
|
+
const rules = { ...security.rules };
|
|
1843
|
+
for (const id of ruleIds) {
|
|
1844
|
+
delete rules[id];
|
|
1845
|
+
}
|
|
1846
|
+
security.rules = rules;
|
|
1847
|
+
result.security = security;
|
|
1848
|
+
}
|
|
1849
|
+
return result;
|
|
1850
|
+
}
|
|
1851
|
+
|
|
1317
1852
|
// src/shared/parsers/typescript.ts
|
|
1318
1853
|
var import_typescript_estree = require("@typescript-eslint/typescript-estree");
|
|
1319
1854
|
|
|
@@ -1339,11 +1874,11 @@ function walk(node, visitor) {
|
|
|
1339
1874
|
var TypeScriptParser = class {
|
|
1340
1875
|
name = "typescript";
|
|
1341
1876
|
extensions = [".ts", ".tsx", ".mts", ".cts"];
|
|
1342
|
-
async parseFile(
|
|
1343
|
-
const contentResult = await readFileContent(
|
|
1877
|
+
async parseFile(path13) {
|
|
1878
|
+
const contentResult = await readFileContent(path13);
|
|
1344
1879
|
if (!contentResult.ok) {
|
|
1345
1880
|
return (0, import_types.Err)(
|
|
1346
|
-
createParseError("NOT_FOUND", `File not found: ${
|
|
1881
|
+
createParseError("NOT_FOUND", `File not found: ${path13}`, { path: path13 }, [
|
|
1347
1882
|
"Check that the file exists",
|
|
1348
1883
|
"Verify the path is correct"
|
|
1349
1884
|
])
|
|
@@ -1353,7 +1888,7 @@ var TypeScriptParser = class {
|
|
|
1353
1888
|
const ast = (0, import_typescript_estree.parse)(contentResult.value, {
|
|
1354
1889
|
loc: true,
|
|
1355
1890
|
range: true,
|
|
1356
|
-
jsx:
|
|
1891
|
+
jsx: path13.endsWith(".tsx"),
|
|
1357
1892
|
errorOnUnknownASTType: false
|
|
1358
1893
|
});
|
|
1359
1894
|
return (0, import_types.Ok)({
|
|
@@ -1364,7 +1899,7 @@ var TypeScriptParser = class {
|
|
|
1364
1899
|
} catch (e) {
|
|
1365
1900
|
const error = e;
|
|
1366
1901
|
return (0, import_types.Err)(
|
|
1367
|
-
createParseError("SYNTAX_ERROR", `Failed to parse ${
|
|
1902
|
+
createParseError("SYNTAX_ERROR", `Failed to parse ${path13}: ${error.message}`, { path: path13 }, [
|
|
1368
1903
|
"Check for syntax errors in the file",
|
|
1369
1904
|
"Ensure valid TypeScript syntax"
|
|
1370
1905
|
])
|
|
@@ -1531,7 +2066,7 @@ var TypeScriptParser = class {
|
|
|
1531
2066
|
|
|
1532
2067
|
// src/entropy/snapshot.ts
|
|
1533
2068
|
var import_path6 = require("path");
|
|
1534
|
-
var
|
|
2069
|
+
var import_minimatch3 = require("minimatch");
|
|
1535
2070
|
async function resolveEntryPoints(rootDir, explicitEntries) {
|
|
1536
2071
|
if (explicitEntries && explicitEntries.length > 0) {
|
|
1537
2072
|
const resolved = explicitEntries.map((e) => (0, import_path6.resolve)(rootDir, e));
|
|
@@ -1648,22 +2183,22 @@ function extractInlineRefs(content) {
|
|
|
1648
2183
|
}
|
|
1649
2184
|
return refs;
|
|
1650
2185
|
}
|
|
1651
|
-
async function parseDocumentationFile(
|
|
1652
|
-
const contentResult = await readFileContent(
|
|
2186
|
+
async function parseDocumentationFile(path13) {
|
|
2187
|
+
const contentResult = await readFileContent(path13);
|
|
1653
2188
|
if (!contentResult.ok) {
|
|
1654
2189
|
return (0, import_types.Err)(
|
|
1655
2190
|
createEntropyError(
|
|
1656
2191
|
"PARSE_ERROR",
|
|
1657
|
-
`Failed to read documentation file: ${
|
|
1658
|
-
{ file:
|
|
2192
|
+
`Failed to read documentation file: ${path13}`,
|
|
2193
|
+
{ file: path13 },
|
|
1659
2194
|
["Check that the file exists"]
|
|
1660
2195
|
)
|
|
1661
2196
|
);
|
|
1662
2197
|
}
|
|
1663
2198
|
const content = contentResult.value;
|
|
1664
|
-
const type =
|
|
2199
|
+
const type = path13.endsWith(".md") ? "markdown" : "text";
|
|
1665
2200
|
return (0, import_types.Ok)({
|
|
1666
|
-
path:
|
|
2201
|
+
path: path13,
|
|
1667
2202
|
type,
|
|
1668
2203
|
content,
|
|
1669
2204
|
codeBlocks: extractCodeBlocks(content),
|
|
@@ -1795,7 +2330,7 @@ async function buildSnapshot(config) {
|
|
|
1795
2330
|
}
|
|
1796
2331
|
sourceFilePaths = sourceFilePaths.filter((f) => {
|
|
1797
2332
|
const rel = (0, import_path6.relative)(rootDir, f);
|
|
1798
|
-
return !excludePatterns.some((p) => (0,
|
|
2333
|
+
return !excludePatterns.some((p) => (0, import_minimatch3.minimatch)(rel, p));
|
|
1799
2334
|
});
|
|
1800
2335
|
const files = [];
|
|
1801
2336
|
for (const filePath of sourceFilePaths) {
|
|
@@ -2325,11 +2860,11 @@ async function detectDeadCode(snapshot, graphDeadCodeData) {
|
|
|
2325
2860
|
}
|
|
2326
2861
|
|
|
2327
2862
|
// src/entropy/detectors/patterns.ts
|
|
2328
|
-
var
|
|
2863
|
+
var import_minimatch4 = require("minimatch");
|
|
2329
2864
|
var import_path9 = require("path");
|
|
2330
2865
|
function fileMatchesPattern(filePath, pattern, rootDir) {
|
|
2331
2866
|
const relativePath = (0, import_path9.relative)(rootDir, filePath);
|
|
2332
|
-
return (0,
|
|
2867
|
+
return (0, import_minimatch4.minimatch)(relativePath, pattern);
|
|
2333
2868
|
}
|
|
2334
2869
|
function checkConfigPattern(pattern, file, rootDir) {
|
|
2335
2870
|
const matches = [];
|
|
@@ -3331,14 +3866,14 @@ var EntropyAnalyzer = class {
|
|
|
3331
3866
|
};
|
|
3332
3867
|
|
|
3333
3868
|
// src/entropy/fixers/safe-fixes.ts
|
|
3334
|
-
var
|
|
3869
|
+
var fs3 = __toESM(require("fs"));
|
|
3335
3870
|
var import_util2 = require("util");
|
|
3336
3871
|
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)(
|
|
3872
|
+
var readFile5 = (0, import_util2.promisify)(fs3.readFile);
|
|
3873
|
+
var writeFile3 = (0, import_util2.promisify)(fs3.writeFile);
|
|
3874
|
+
var unlink2 = (0, import_util2.promisify)(fs3.unlink);
|
|
3875
|
+
var mkdir2 = (0, import_util2.promisify)(fs3.mkdir);
|
|
3876
|
+
var copyFile2 = (0, import_util2.promisify)(fs3.copyFile);
|
|
3342
3877
|
var DEFAULT_FIX_CONFIG = {
|
|
3343
3878
|
dryRun: false,
|
|
3344
3879
|
fixTypes: ["unused-imports", "dead-files"],
|
|
@@ -3465,25 +4000,25 @@ async function applySingleFix(fix, config) {
|
|
|
3465
4000
|
break;
|
|
3466
4001
|
case "delete-lines":
|
|
3467
4002
|
if (fix.line !== void 0) {
|
|
3468
|
-
const content = await
|
|
4003
|
+
const content = await readFile5(fix.file, "utf-8");
|
|
3469
4004
|
const lines = content.split("\n");
|
|
3470
4005
|
lines.splice(fix.line - 1, 1);
|
|
3471
|
-
await
|
|
4006
|
+
await writeFile3(fix.file, lines.join("\n"));
|
|
3472
4007
|
}
|
|
3473
4008
|
break;
|
|
3474
4009
|
case "replace":
|
|
3475
4010
|
if (fix.oldContent && fix.newContent !== void 0) {
|
|
3476
|
-
const content = await
|
|
4011
|
+
const content = await readFile5(fix.file, "utf-8");
|
|
3477
4012
|
const newContent = content.replace(fix.oldContent, fix.newContent);
|
|
3478
|
-
await
|
|
4013
|
+
await writeFile3(fix.file, newContent);
|
|
3479
4014
|
}
|
|
3480
4015
|
break;
|
|
3481
4016
|
case "insert":
|
|
3482
4017
|
if (fix.line !== void 0 && fix.newContent) {
|
|
3483
|
-
const content = await
|
|
4018
|
+
const content = await readFile5(fix.file, "utf-8");
|
|
3484
4019
|
const lines = content.split("\n");
|
|
3485
4020
|
lines.splice(fix.line - 1, 0, fix.newContent);
|
|
3486
|
-
await
|
|
4021
|
+
await writeFile3(fix.file, lines.join("\n"));
|
|
3487
4022
|
}
|
|
3488
4023
|
break;
|
|
3489
4024
|
}
|
|
@@ -3660,46 +4195,46 @@ function deduplicateCleanupFindings(findings) {
|
|
|
3660
4195
|
}
|
|
3661
4196
|
|
|
3662
4197
|
// src/entropy/config/schema.ts
|
|
3663
|
-
var
|
|
3664
|
-
var MustExportRuleSchema =
|
|
3665
|
-
type:
|
|
3666
|
-
names:
|
|
4198
|
+
var import_zod2 = require("zod");
|
|
4199
|
+
var MustExportRuleSchema = import_zod2.z.object({
|
|
4200
|
+
type: import_zod2.z.literal("must-export"),
|
|
4201
|
+
names: import_zod2.z.array(import_zod2.z.string())
|
|
3667
4202
|
});
|
|
3668
|
-
var MustExportDefaultRuleSchema =
|
|
3669
|
-
type:
|
|
3670
|
-
kind:
|
|
4203
|
+
var MustExportDefaultRuleSchema = import_zod2.z.object({
|
|
4204
|
+
type: import_zod2.z.literal("must-export-default"),
|
|
4205
|
+
kind: import_zod2.z.enum(["class", "function", "object"]).optional()
|
|
3671
4206
|
});
|
|
3672
|
-
var NoExportRuleSchema =
|
|
3673
|
-
type:
|
|
3674
|
-
names:
|
|
4207
|
+
var NoExportRuleSchema = import_zod2.z.object({
|
|
4208
|
+
type: import_zod2.z.literal("no-export"),
|
|
4209
|
+
names: import_zod2.z.array(import_zod2.z.string())
|
|
3675
4210
|
});
|
|
3676
|
-
var MustImportRuleSchema =
|
|
3677
|
-
type:
|
|
3678
|
-
from:
|
|
3679
|
-
names:
|
|
4211
|
+
var MustImportRuleSchema = import_zod2.z.object({
|
|
4212
|
+
type: import_zod2.z.literal("must-import"),
|
|
4213
|
+
from: import_zod2.z.string(),
|
|
4214
|
+
names: import_zod2.z.array(import_zod2.z.string()).optional()
|
|
3680
4215
|
});
|
|
3681
|
-
var NoImportRuleSchema =
|
|
3682
|
-
type:
|
|
3683
|
-
from:
|
|
4216
|
+
var NoImportRuleSchema = import_zod2.z.object({
|
|
4217
|
+
type: import_zod2.z.literal("no-import"),
|
|
4218
|
+
from: import_zod2.z.string()
|
|
3684
4219
|
});
|
|
3685
|
-
var NamingRuleSchema =
|
|
3686
|
-
type:
|
|
3687
|
-
match:
|
|
3688
|
-
convention:
|
|
4220
|
+
var NamingRuleSchema = import_zod2.z.object({
|
|
4221
|
+
type: import_zod2.z.literal("naming"),
|
|
4222
|
+
match: import_zod2.z.string(),
|
|
4223
|
+
convention: import_zod2.z.enum(["camelCase", "PascalCase", "UPPER_SNAKE", "kebab-case"])
|
|
3689
4224
|
});
|
|
3690
|
-
var MaxExportsRuleSchema =
|
|
3691
|
-
type:
|
|
3692
|
-
count:
|
|
4225
|
+
var MaxExportsRuleSchema = import_zod2.z.object({
|
|
4226
|
+
type: import_zod2.z.literal("max-exports"),
|
|
4227
|
+
count: import_zod2.z.number().positive()
|
|
3693
4228
|
});
|
|
3694
|
-
var MaxLinesRuleSchema =
|
|
3695
|
-
type:
|
|
3696
|
-
count:
|
|
4229
|
+
var MaxLinesRuleSchema = import_zod2.z.object({
|
|
4230
|
+
type: import_zod2.z.literal("max-lines"),
|
|
4231
|
+
count: import_zod2.z.number().positive()
|
|
3697
4232
|
});
|
|
3698
|
-
var RequireJSDocRuleSchema =
|
|
3699
|
-
type:
|
|
3700
|
-
for:
|
|
4233
|
+
var RequireJSDocRuleSchema = import_zod2.z.object({
|
|
4234
|
+
type: import_zod2.z.literal("require-jsdoc"),
|
|
4235
|
+
for: import_zod2.z.array(import_zod2.z.enum(["function", "class", "export"]))
|
|
3701
4236
|
});
|
|
3702
|
-
var RuleSchema =
|
|
4237
|
+
var RuleSchema = import_zod2.z.discriminatedUnion("type", [
|
|
3703
4238
|
MustExportRuleSchema,
|
|
3704
4239
|
MustExportDefaultRuleSchema,
|
|
3705
4240
|
NoExportRuleSchema,
|
|
@@ -3710,47 +4245,47 @@ var RuleSchema = import_zod.z.discriminatedUnion("type", [
|
|
|
3710
4245
|
MaxLinesRuleSchema,
|
|
3711
4246
|
RequireJSDocRuleSchema
|
|
3712
4247
|
]);
|
|
3713
|
-
var ConfigPatternSchema =
|
|
3714
|
-
name:
|
|
3715
|
-
description:
|
|
3716
|
-
severity:
|
|
3717
|
-
files:
|
|
4248
|
+
var ConfigPatternSchema = import_zod2.z.object({
|
|
4249
|
+
name: import_zod2.z.string().min(1),
|
|
4250
|
+
description: import_zod2.z.string(),
|
|
4251
|
+
severity: import_zod2.z.enum(["error", "warning"]),
|
|
4252
|
+
files: import_zod2.z.array(import_zod2.z.string()),
|
|
3718
4253
|
rule: RuleSchema,
|
|
3719
|
-
message:
|
|
4254
|
+
message: import_zod2.z.string().optional()
|
|
3720
4255
|
});
|
|
3721
|
-
var PatternConfigSchema =
|
|
3722
|
-
patterns:
|
|
3723
|
-
customPatterns:
|
|
4256
|
+
var PatternConfigSchema = import_zod2.z.object({
|
|
4257
|
+
patterns: import_zod2.z.array(ConfigPatternSchema),
|
|
4258
|
+
customPatterns: import_zod2.z.array(import_zod2.z.any()).optional(),
|
|
3724
4259
|
// Code patterns are functions, can't validate
|
|
3725
|
-
ignoreFiles:
|
|
4260
|
+
ignoreFiles: import_zod2.z.array(import_zod2.z.string()).optional()
|
|
3726
4261
|
});
|
|
3727
|
-
var DriftConfigSchema =
|
|
3728
|
-
docPaths:
|
|
3729
|
-
checkApiSignatures:
|
|
3730
|
-
checkExamples:
|
|
3731
|
-
checkStructure:
|
|
3732
|
-
ignorePatterns:
|
|
4262
|
+
var DriftConfigSchema = import_zod2.z.object({
|
|
4263
|
+
docPaths: import_zod2.z.array(import_zod2.z.string()).optional(),
|
|
4264
|
+
checkApiSignatures: import_zod2.z.boolean().optional(),
|
|
4265
|
+
checkExamples: import_zod2.z.boolean().optional(),
|
|
4266
|
+
checkStructure: import_zod2.z.boolean().optional(),
|
|
4267
|
+
ignorePatterns: import_zod2.z.array(import_zod2.z.string()).optional()
|
|
3733
4268
|
});
|
|
3734
|
-
var DeadCodeConfigSchema =
|
|
3735
|
-
entryPoints:
|
|
3736
|
-
includeTypes:
|
|
3737
|
-
includeInternals:
|
|
3738
|
-
ignorePatterns:
|
|
3739
|
-
treatDynamicImportsAs:
|
|
4269
|
+
var DeadCodeConfigSchema = import_zod2.z.object({
|
|
4270
|
+
entryPoints: import_zod2.z.array(import_zod2.z.string()).optional(),
|
|
4271
|
+
includeTypes: import_zod2.z.boolean().optional(),
|
|
4272
|
+
includeInternals: import_zod2.z.boolean().optional(),
|
|
4273
|
+
ignorePatterns: import_zod2.z.array(import_zod2.z.string()).optional(),
|
|
4274
|
+
treatDynamicImportsAs: import_zod2.z.enum(["used", "unknown"]).optional()
|
|
3740
4275
|
});
|
|
3741
|
-
var EntropyConfigSchema =
|
|
3742
|
-
rootDir:
|
|
3743
|
-
parser:
|
|
4276
|
+
var EntropyConfigSchema = import_zod2.z.object({
|
|
4277
|
+
rootDir: import_zod2.z.string(),
|
|
4278
|
+
parser: import_zod2.z.any().optional(),
|
|
3744
4279
|
// LanguageParser instance, can't validate
|
|
3745
|
-
entryPoints:
|
|
3746
|
-
analyze:
|
|
3747
|
-
drift:
|
|
3748
|
-
deadCode:
|
|
3749
|
-
patterns:
|
|
4280
|
+
entryPoints: import_zod2.z.array(import_zod2.z.string()).optional(),
|
|
4281
|
+
analyze: import_zod2.z.object({
|
|
4282
|
+
drift: import_zod2.z.union([import_zod2.z.boolean(), DriftConfigSchema]).optional(),
|
|
4283
|
+
deadCode: import_zod2.z.union([import_zod2.z.boolean(), DeadCodeConfigSchema]).optional(),
|
|
4284
|
+
patterns: import_zod2.z.union([import_zod2.z.boolean(), PatternConfigSchema]).optional()
|
|
3750
4285
|
}),
|
|
3751
|
-
include:
|
|
3752
|
-
exclude:
|
|
3753
|
-
docPaths:
|
|
4286
|
+
include: import_zod2.z.array(import_zod2.z.string()).optional(),
|
|
4287
|
+
exclude: import_zod2.z.array(import_zod2.z.string()).optional(),
|
|
4288
|
+
docPaths: import_zod2.z.array(import_zod2.z.string()).optional()
|
|
3754
4289
|
});
|
|
3755
4290
|
function validatePatternConfig(config) {
|
|
3756
4291
|
const result = PatternConfigSchema.safeParse(config);
|
|
@@ -4007,7 +4542,7 @@ var RegressionDetector = class {
|
|
|
4007
4542
|
};
|
|
4008
4543
|
|
|
4009
4544
|
// src/performance/critical-path.ts
|
|
4010
|
-
var
|
|
4545
|
+
var fs4 = __toESM(require("fs"));
|
|
4011
4546
|
var path = __toESM(require("path"));
|
|
4012
4547
|
var SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", "dist", ".git"]);
|
|
4013
4548
|
var SOURCE_EXTENSIONS = /* @__PURE__ */ new Set([".ts", ".tsx", ".js", ".jsx"]);
|
|
@@ -4059,7 +4594,7 @@ var CriticalPathResolver = class {
|
|
|
4059
4594
|
walkDir(dir, entries) {
|
|
4060
4595
|
let items;
|
|
4061
4596
|
try {
|
|
4062
|
-
items =
|
|
4597
|
+
items = fs4.readdirSync(dir, { withFileTypes: true });
|
|
4063
4598
|
} catch {
|
|
4064
4599
|
return;
|
|
4065
4600
|
}
|
|
@@ -4075,7 +4610,7 @@ var CriticalPathResolver = class {
|
|
|
4075
4610
|
scanFile(filePath, entries) {
|
|
4076
4611
|
let content;
|
|
4077
4612
|
try {
|
|
4078
|
-
content =
|
|
4613
|
+
content = fs4.readFileSync(filePath, "utf-8");
|
|
4079
4614
|
} catch {
|
|
4080
4615
|
return;
|
|
4081
4616
|
}
|
|
@@ -4254,17 +4789,17 @@ function resetFeedbackConfig() {
|
|
|
4254
4789
|
}
|
|
4255
4790
|
|
|
4256
4791
|
// src/feedback/review/diff-analyzer.ts
|
|
4257
|
-
function parseDiff(
|
|
4792
|
+
function parseDiff(diff2) {
|
|
4258
4793
|
try {
|
|
4259
|
-
if (!
|
|
4260
|
-
return (0, import_types.Ok)({ diff, files: [] });
|
|
4794
|
+
if (!diff2.trim()) {
|
|
4795
|
+
return (0, import_types.Ok)({ diff: diff2, files: [] });
|
|
4261
4796
|
}
|
|
4262
4797
|
const files = [];
|
|
4263
4798
|
const newFileRegex = /new file mode/;
|
|
4264
4799
|
const deletedFileRegex = /deleted file mode/;
|
|
4265
4800
|
const additionRegex = /^\+(?!\+\+)/gm;
|
|
4266
4801
|
const deletionRegex = /^-(?!--)/gm;
|
|
4267
|
-
const diffParts =
|
|
4802
|
+
const diffParts = diff2.split(/(?=diff --git)/);
|
|
4268
4803
|
for (const part of diffParts) {
|
|
4269
4804
|
if (!part.trim()) continue;
|
|
4270
4805
|
const headerMatch = /diff --git a\/(.+?) b\/(.+?)(?:\n|$)/.exec(part);
|
|
@@ -4287,7 +4822,7 @@ function parseDiff(diff) {
|
|
|
4287
4822
|
deletions
|
|
4288
4823
|
});
|
|
4289
4824
|
}
|
|
4290
|
-
return (0, import_types.Ok)({ diff, files });
|
|
4825
|
+
return (0, import_types.Ok)({ diff: diff2, files });
|
|
4291
4826
|
} catch (error) {
|
|
4292
4827
|
return (0, import_types.Err)({
|
|
4293
4828
|
code: "DIFF_PARSE_ERROR",
|
|
@@ -4891,77 +5426,1265 @@ var NoOpSink = class {
|
|
|
4891
5426
|
}
|
|
4892
5427
|
};
|
|
4893
5428
|
|
|
5429
|
+
// src/architecture/types.ts
|
|
5430
|
+
var import_zod3 = require("zod");
|
|
5431
|
+
var ArchMetricCategorySchema = import_zod3.z.enum([
|
|
5432
|
+
"circular-deps",
|
|
5433
|
+
"layer-violations",
|
|
5434
|
+
"complexity",
|
|
5435
|
+
"coupling",
|
|
5436
|
+
"forbidden-imports",
|
|
5437
|
+
"module-size",
|
|
5438
|
+
"dependency-depth"
|
|
5439
|
+
]);
|
|
5440
|
+
var ViolationSchema = import_zod3.z.object({
|
|
5441
|
+
id: import_zod3.z.string(),
|
|
5442
|
+
// stable hash: sha256(relativePath + ':' + category + ':' + normalizedDetail)
|
|
5443
|
+
file: import_zod3.z.string(),
|
|
5444
|
+
// relative to project root
|
|
5445
|
+
category: ArchMetricCategorySchema.optional(),
|
|
5446
|
+
// context for baseline reporting
|
|
5447
|
+
detail: import_zod3.z.string(),
|
|
5448
|
+
// human-readable description
|
|
5449
|
+
severity: import_zod3.z.enum(["error", "warning"])
|
|
5450
|
+
});
|
|
5451
|
+
var MetricResultSchema = import_zod3.z.object({
|
|
5452
|
+
category: ArchMetricCategorySchema,
|
|
5453
|
+
scope: import_zod3.z.string(),
|
|
5454
|
+
// e.g., 'project', 'src/services', 'src/api/routes.ts'
|
|
5455
|
+
value: import_zod3.z.number(),
|
|
5456
|
+
// numeric metric (violation count, complexity score, etc.)
|
|
5457
|
+
violations: import_zod3.z.array(ViolationSchema),
|
|
5458
|
+
metadata: import_zod3.z.record(import_zod3.z.unknown()).optional()
|
|
5459
|
+
});
|
|
5460
|
+
var CategoryBaselineSchema = import_zod3.z.object({
|
|
5461
|
+
value: import_zod3.z.number(),
|
|
5462
|
+
// aggregate metric value at baseline time
|
|
5463
|
+
violationIds: import_zod3.z.array(import_zod3.z.string())
|
|
5464
|
+
// stable IDs of known violations (the allowlist)
|
|
5465
|
+
});
|
|
5466
|
+
var ArchBaselineSchema = import_zod3.z.object({
|
|
5467
|
+
version: import_zod3.z.literal(1),
|
|
5468
|
+
updatedAt: import_zod3.z.string().datetime(),
|
|
5469
|
+
// ISO 8601
|
|
5470
|
+
updatedFrom: import_zod3.z.string(),
|
|
5471
|
+
// commit hash
|
|
5472
|
+
metrics: import_zod3.z.record(ArchMetricCategorySchema, CategoryBaselineSchema)
|
|
5473
|
+
});
|
|
5474
|
+
var CategoryRegressionSchema = import_zod3.z.object({
|
|
5475
|
+
category: ArchMetricCategorySchema,
|
|
5476
|
+
baselineValue: import_zod3.z.number(),
|
|
5477
|
+
currentValue: import_zod3.z.number(),
|
|
5478
|
+
delta: import_zod3.z.number()
|
|
5479
|
+
});
|
|
5480
|
+
var ArchDiffResultSchema = import_zod3.z.object({
|
|
5481
|
+
passed: import_zod3.z.boolean(),
|
|
5482
|
+
newViolations: import_zod3.z.array(ViolationSchema),
|
|
5483
|
+
// in current but not in baseline -> FAIL
|
|
5484
|
+
resolvedViolations: import_zod3.z.array(import_zod3.z.string()),
|
|
5485
|
+
// in baseline but not in current -> celebrate
|
|
5486
|
+
preExisting: import_zod3.z.array(import_zod3.z.string()),
|
|
5487
|
+
// in both -> allowed, tracked
|
|
5488
|
+
regressions: import_zod3.z.array(CategoryRegressionSchema)
|
|
5489
|
+
// aggregate value exceeded baseline
|
|
5490
|
+
});
|
|
5491
|
+
var ThresholdConfigSchema = import_zod3.z.record(
|
|
5492
|
+
ArchMetricCategorySchema,
|
|
5493
|
+
import_zod3.z.union([import_zod3.z.number(), import_zod3.z.record(import_zod3.z.string(), import_zod3.z.number())])
|
|
5494
|
+
);
|
|
5495
|
+
var ArchConfigSchema = import_zod3.z.object({
|
|
5496
|
+
enabled: import_zod3.z.boolean().default(true),
|
|
5497
|
+
baselinePath: import_zod3.z.string().default(".harness/arch/baselines.json"),
|
|
5498
|
+
thresholds: ThresholdConfigSchema.default({}),
|
|
5499
|
+
modules: import_zod3.z.record(import_zod3.z.string(), ThresholdConfigSchema).default({})
|
|
5500
|
+
});
|
|
5501
|
+
var ConstraintRuleSchema = import_zod3.z.object({
|
|
5502
|
+
id: import_zod3.z.string(),
|
|
5503
|
+
// stable hash: sha256(category + ':' + scope + ':' + description)
|
|
5504
|
+
category: ArchMetricCategorySchema,
|
|
5505
|
+
description: import_zod3.z.string(),
|
|
5506
|
+
// e.g., "Layer 'services' must not import from 'ui'"
|
|
5507
|
+
scope: import_zod3.z.string(),
|
|
5508
|
+
// e.g., 'src/services/', 'project'
|
|
5509
|
+
targets: import_zod3.z.array(import_zod3.z.string()).optional()
|
|
5510
|
+
// forward-compat for governs edges
|
|
5511
|
+
});
|
|
5512
|
+
|
|
5513
|
+
// src/architecture/collectors/circular-deps.ts
|
|
5514
|
+
var import_node_path3 = require("path");
|
|
5515
|
+
|
|
5516
|
+
// src/architecture/collectors/hash.ts
|
|
5517
|
+
var import_node_crypto = require("crypto");
|
|
5518
|
+
function violationId(relativePath, category, normalizedDetail) {
|
|
5519
|
+
const path13 = relativePath.replace(/\\/g, "/");
|
|
5520
|
+
const input = `${path13}:${category}:${normalizedDetail}`;
|
|
5521
|
+
return (0, import_node_crypto.createHash)("sha256").update(input).digest("hex");
|
|
5522
|
+
}
|
|
5523
|
+
function constraintRuleId(category, scope, description) {
|
|
5524
|
+
const input = `${category}:${scope}:${description}`;
|
|
5525
|
+
return (0, import_node_crypto.createHash)("sha256").update(input).digest("hex");
|
|
5526
|
+
}
|
|
5527
|
+
|
|
5528
|
+
// src/architecture/collectors/circular-deps.ts
|
|
5529
|
+
var CircularDepsCollector = class {
|
|
5530
|
+
category = "circular-deps";
|
|
5531
|
+
getRules(_config, _rootDir) {
|
|
5532
|
+
const description = "No circular dependencies allowed";
|
|
5533
|
+
return [
|
|
5534
|
+
{
|
|
5535
|
+
id: constraintRuleId(this.category, "project", description),
|
|
5536
|
+
category: this.category,
|
|
5537
|
+
description,
|
|
5538
|
+
scope: "project"
|
|
5539
|
+
}
|
|
5540
|
+
];
|
|
5541
|
+
}
|
|
5542
|
+
async collect(_config, rootDir) {
|
|
5543
|
+
const files = await findFiles("**/*.ts", rootDir);
|
|
5544
|
+
const stubParser = {
|
|
5545
|
+
name: "typescript",
|
|
5546
|
+
extensions: [".ts", ".tsx"],
|
|
5547
|
+
parseFile: async () => ({ ok: false, error: { code: "PARSE_ERROR", message: "not needed" } }),
|
|
5548
|
+
extractImports: () => ({
|
|
5549
|
+
ok: false,
|
|
5550
|
+
error: { code: "EXTRACT_ERROR", message: "not needed" }
|
|
5551
|
+
}),
|
|
5552
|
+
extractExports: () => ({
|
|
5553
|
+
ok: false,
|
|
5554
|
+
error: { code: "EXTRACT_ERROR", message: "not needed" }
|
|
5555
|
+
}),
|
|
5556
|
+
health: async () => ({ ok: true, value: { available: true } })
|
|
5557
|
+
};
|
|
5558
|
+
const graphResult = await buildDependencyGraph(files, stubParser);
|
|
5559
|
+
if (!graphResult.ok) {
|
|
5560
|
+
return [
|
|
5561
|
+
{
|
|
5562
|
+
category: this.category,
|
|
5563
|
+
scope: "project",
|
|
5564
|
+
value: 0,
|
|
5565
|
+
violations: [],
|
|
5566
|
+
metadata: { error: "Failed to build dependency graph" }
|
|
5567
|
+
}
|
|
5568
|
+
];
|
|
5569
|
+
}
|
|
5570
|
+
const result = detectCircularDeps(graphResult.value);
|
|
5571
|
+
if (!result.ok) {
|
|
5572
|
+
return [
|
|
5573
|
+
{
|
|
5574
|
+
category: this.category,
|
|
5575
|
+
scope: "project",
|
|
5576
|
+
value: 0,
|
|
5577
|
+
violations: [],
|
|
5578
|
+
metadata: { error: "Failed to detect circular deps" }
|
|
5579
|
+
}
|
|
5580
|
+
];
|
|
5581
|
+
}
|
|
5582
|
+
const { cycles, largestCycle } = result.value;
|
|
5583
|
+
const violations = cycles.map((cycle) => {
|
|
5584
|
+
const cyclePath = cycle.cycle.map((f) => (0, import_node_path3.relative)(rootDir, f)).join(" -> ");
|
|
5585
|
+
const firstFile = (0, import_node_path3.relative)(rootDir, cycle.cycle[0]);
|
|
5586
|
+
return {
|
|
5587
|
+
id: violationId(firstFile, this.category, cyclePath),
|
|
5588
|
+
file: firstFile,
|
|
5589
|
+
detail: `Circular dependency: ${cyclePath}`,
|
|
5590
|
+
severity: cycle.severity
|
|
5591
|
+
};
|
|
5592
|
+
});
|
|
5593
|
+
return [
|
|
5594
|
+
{
|
|
5595
|
+
category: this.category,
|
|
5596
|
+
scope: "project",
|
|
5597
|
+
value: cycles.length,
|
|
5598
|
+
violations,
|
|
5599
|
+
metadata: { largestCycle, cycleCount: cycles.length }
|
|
5600
|
+
}
|
|
5601
|
+
];
|
|
5602
|
+
}
|
|
5603
|
+
};
|
|
5604
|
+
|
|
5605
|
+
// src/architecture/collectors/layer-violations.ts
|
|
5606
|
+
var import_node_path4 = require("path");
|
|
5607
|
+
var LayerViolationCollector = class {
|
|
5608
|
+
category = "layer-violations";
|
|
5609
|
+
getRules(_config, _rootDir) {
|
|
5610
|
+
const description = "No layer boundary violations allowed";
|
|
5611
|
+
return [
|
|
5612
|
+
{
|
|
5613
|
+
id: constraintRuleId(this.category, "project", description),
|
|
5614
|
+
category: this.category,
|
|
5615
|
+
description,
|
|
5616
|
+
scope: "project"
|
|
5617
|
+
}
|
|
5618
|
+
];
|
|
5619
|
+
}
|
|
5620
|
+
async collect(_config, rootDir) {
|
|
5621
|
+
const stubParser = {
|
|
5622
|
+
name: "typescript",
|
|
5623
|
+
extensions: [".ts", ".tsx"],
|
|
5624
|
+
parseFile: async () => ({ ok: false, error: { code: "PARSE_ERROR", message: "" } }),
|
|
5625
|
+
extractImports: () => ({ ok: false, error: { code: "EXTRACT_ERROR", message: "" } }),
|
|
5626
|
+
extractExports: () => ({ ok: false, error: { code: "EXTRACT_ERROR", message: "" } }),
|
|
5627
|
+
health: async () => ({ ok: true, value: { available: true } })
|
|
5628
|
+
};
|
|
5629
|
+
const result = await validateDependencies({
|
|
5630
|
+
layers: [],
|
|
5631
|
+
rootDir,
|
|
5632
|
+
parser: stubParser,
|
|
5633
|
+
fallbackBehavior: "skip"
|
|
5634
|
+
});
|
|
5635
|
+
if (!result.ok) {
|
|
5636
|
+
return [
|
|
5637
|
+
{
|
|
5638
|
+
category: this.category,
|
|
5639
|
+
scope: "project",
|
|
5640
|
+
value: 0,
|
|
5641
|
+
violations: [],
|
|
5642
|
+
metadata: { error: "Failed to validate dependencies" }
|
|
5643
|
+
}
|
|
5644
|
+
];
|
|
5645
|
+
}
|
|
5646
|
+
const layerViolations = result.value.violations.filter(
|
|
5647
|
+
(v) => v.reason === "WRONG_LAYER"
|
|
5648
|
+
);
|
|
5649
|
+
const violations = layerViolations.map((v) => {
|
|
5650
|
+
const relFile = (0, import_node_path4.relative)(rootDir, v.file);
|
|
5651
|
+
const relImport = (0, import_node_path4.relative)(rootDir, v.imports);
|
|
5652
|
+
const detail = `${v.fromLayer} -> ${v.toLayer}: ${relFile} imports ${relImport}`;
|
|
5653
|
+
return {
|
|
5654
|
+
id: violationId(relFile, this.category, detail),
|
|
5655
|
+
file: relFile,
|
|
5656
|
+
category: this.category,
|
|
5657
|
+
detail,
|
|
5658
|
+
severity: "error"
|
|
5659
|
+
};
|
|
5660
|
+
});
|
|
5661
|
+
return [
|
|
5662
|
+
{
|
|
5663
|
+
category: this.category,
|
|
5664
|
+
scope: "project",
|
|
5665
|
+
value: violations.length,
|
|
5666
|
+
violations
|
|
5667
|
+
}
|
|
5668
|
+
];
|
|
5669
|
+
}
|
|
5670
|
+
};
|
|
5671
|
+
|
|
5672
|
+
// src/architecture/collectors/complexity.ts
|
|
5673
|
+
var import_node_path5 = require("path");
|
|
5674
|
+
var ComplexityCollector = class {
|
|
5675
|
+
category = "complexity";
|
|
5676
|
+
getRules(_config, _rootDir) {
|
|
5677
|
+
const description = "Cyclomatic complexity must stay within thresholds";
|
|
5678
|
+
return [
|
|
5679
|
+
{
|
|
5680
|
+
id: constraintRuleId(this.category, "project", description),
|
|
5681
|
+
category: this.category,
|
|
5682
|
+
description,
|
|
5683
|
+
scope: "project"
|
|
5684
|
+
}
|
|
5685
|
+
];
|
|
5686
|
+
}
|
|
5687
|
+
async collect(_config, rootDir) {
|
|
5688
|
+
const files = await findFiles("**/*.ts", rootDir);
|
|
5689
|
+
const snapshot = {
|
|
5690
|
+
files: files.map((f) => ({
|
|
5691
|
+
path: f,
|
|
5692
|
+
ast: { type: "Program", body: null, language: "typescript" },
|
|
5693
|
+
imports: [],
|
|
5694
|
+
exports: [],
|
|
5695
|
+
internalSymbols: [],
|
|
5696
|
+
jsDocComments: []
|
|
5697
|
+
})),
|
|
5698
|
+
dependencyGraph: { nodes: [], edges: [] },
|
|
5699
|
+
exportMap: { byFile: /* @__PURE__ */ new Map(), byName: /* @__PURE__ */ new Map() },
|
|
5700
|
+
docs: [],
|
|
5701
|
+
codeReferences: [],
|
|
5702
|
+
entryPoints: [],
|
|
5703
|
+
rootDir,
|
|
5704
|
+
config: { rootDir, analyze: {} },
|
|
5705
|
+
buildTime: 0
|
|
5706
|
+
};
|
|
5707
|
+
const complexityThreshold = _config.thresholds.complexity;
|
|
5708
|
+
const maxComplexity = typeof complexityThreshold === "number" ? complexityThreshold : complexityThreshold?.max ?? 15;
|
|
5709
|
+
const complexityConfig = {
|
|
5710
|
+
thresholds: {
|
|
5711
|
+
cyclomaticComplexity: {
|
|
5712
|
+
error: maxComplexity,
|
|
5713
|
+
warn: Math.floor(maxComplexity * 0.7)
|
|
5714
|
+
}
|
|
5715
|
+
}
|
|
5716
|
+
};
|
|
5717
|
+
const result = await detectComplexityViolations(snapshot, complexityConfig);
|
|
5718
|
+
if (!result.ok) {
|
|
5719
|
+
return [
|
|
5720
|
+
{
|
|
5721
|
+
category: this.category,
|
|
5722
|
+
scope: "project",
|
|
5723
|
+
value: 0,
|
|
5724
|
+
violations: [],
|
|
5725
|
+
metadata: { error: "Failed to detect complexity violations" }
|
|
5726
|
+
}
|
|
5727
|
+
];
|
|
5728
|
+
}
|
|
5729
|
+
const { violations: complexityViolations, stats } = result.value;
|
|
5730
|
+
const filtered = complexityViolations.filter(
|
|
5731
|
+
(v) => v.severity === "error" || v.severity === "warning"
|
|
5732
|
+
);
|
|
5733
|
+
const violations = filtered.map((v) => {
|
|
5734
|
+
const relFile = (0, import_node_path5.relative)(rootDir, v.file);
|
|
5735
|
+
const idDetail = `${v.metric}:${v.function}`;
|
|
5736
|
+
return {
|
|
5737
|
+
id: violationId(relFile, this.category, idDetail),
|
|
5738
|
+
file: relFile,
|
|
5739
|
+
category: this.category,
|
|
5740
|
+
detail: `${v.metric}=${v.value} in ${v.function} (threshold: ${v.threshold})`,
|
|
5741
|
+
severity: v.severity
|
|
5742
|
+
};
|
|
5743
|
+
});
|
|
5744
|
+
return [
|
|
5745
|
+
{
|
|
5746
|
+
category: this.category,
|
|
5747
|
+
scope: "project",
|
|
5748
|
+
value: violations.length,
|
|
5749
|
+
violations,
|
|
5750
|
+
metadata: {
|
|
5751
|
+
filesAnalyzed: stats.filesAnalyzed,
|
|
5752
|
+
functionsAnalyzed: stats.functionsAnalyzed
|
|
5753
|
+
}
|
|
5754
|
+
}
|
|
5755
|
+
];
|
|
5756
|
+
}
|
|
5757
|
+
};
|
|
5758
|
+
|
|
5759
|
+
// src/architecture/collectors/coupling.ts
|
|
5760
|
+
var import_node_path6 = require("path");
|
|
5761
|
+
var CouplingCollector = class {
|
|
5762
|
+
category = "coupling";
|
|
5763
|
+
getRules(_config, _rootDir) {
|
|
5764
|
+
const description = "Coupling metrics must stay within thresholds";
|
|
5765
|
+
return [
|
|
5766
|
+
{
|
|
5767
|
+
id: constraintRuleId(this.category, "project", description),
|
|
5768
|
+
category: this.category,
|
|
5769
|
+
description,
|
|
5770
|
+
scope: "project"
|
|
5771
|
+
}
|
|
5772
|
+
];
|
|
5773
|
+
}
|
|
5774
|
+
async collect(_config, rootDir) {
|
|
5775
|
+
const files = await findFiles("**/*.ts", rootDir);
|
|
5776
|
+
const snapshot = {
|
|
5777
|
+
files: files.map((f) => ({
|
|
5778
|
+
path: f,
|
|
5779
|
+
ast: { type: "Program", body: null, language: "typescript" },
|
|
5780
|
+
imports: [],
|
|
5781
|
+
exports: [],
|
|
5782
|
+
internalSymbols: [],
|
|
5783
|
+
jsDocComments: []
|
|
5784
|
+
})),
|
|
5785
|
+
dependencyGraph: { nodes: [], edges: [] },
|
|
5786
|
+
exportMap: { byFile: /* @__PURE__ */ new Map(), byName: /* @__PURE__ */ new Map() },
|
|
5787
|
+
docs: [],
|
|
5788
|
+
codeReferences: [],
|
|
5789
|
+
entryPoints: [],
|
|
5790
|
+
rootDir,
|
|
5791
|
+
config: { rootDir, analyze: {} },
|
|
5792
|
+
buildTime: 0
|
|
5793
|
+
};
|
|
5794
|
+
const result = await detectCouplingViolations(snapshot);
|
|
5795
|
+
if (!result.ok) {
|
|
5796
|
+
return [
|
|
5797
|
+
{
|
|
5798
|
+
category: this.category,
|
|
5799
|
+
scope: "project",
|
|
5800
|
+
value: 0,
|
|
5801
|
+
violations: [],
|
|
5802
|
+
metadata: { error: "Failed to detect coupling violations" }
|
|
5803
|
+
}
|
|
5804
|
+
];
|
|
5805
|
+
}
|
|
5806
|
+
const { violations: couplingViolations, stats } = result.value;
|
|
5807
|
+
const filtered = couplingViolations.filter(
|
|
5808
|
+
(v) => v.severity === "error" || v.severity === "warning"
|
|
5809
|
+
);
|
|
5810
|
+
const violations = filtered.map((v) => {
|
|
5811
|
+
const relFile = (0, import_node_path6.relative)(rootDir, v.file);
|
|
5812
|
+
const idDetail = `${v.metric}`;
|
|
5813
|
+
return {
|
|
5814
|
+
id: violationId(relFile, this.category, idDetail),
|
|
5815
|
+
file: relFile,
|
|
5816
|
+
category: this.category,
|
|
5817
|
+
detail: `${v.metric}=${v.value} (threshold: ${v.threshold})`,
|
|
5818
|
+
severity: v.severity
|
|
5819
|
+
};
|
|
5820
|
+
});
|
|
5821
|
+
return [
|
|
5822
|
+
{
|
|
5823
|
+
category: this.category,
|
|
5824
|
+
scope: "project",
|
|
5825
|
+
value: violations.length,
|
|
5826
|
+
violations,
|
|
5827
|
+
metadata: { filesAnalyzed: stats.filesAnalyzed }
|
|
5828
|
+
}
|
|
5829
|
+
];
|
|
5830
|
+
}
|
|
5831
|
+
};
|
|
5832
|
+
|
|
5833
|
+
// src/architecture/collectors/forbidden-imports.ts
|
|
5834
|
+
var import_node_path7 = require("path");
|
|
5835
|
+
var ForbiddenImportCollector = class {
|
|
5836
|
+
category = "forbidden-imports";
|
|
5837
|
+
getRules(_config, _rootDir) {
|
|
5838
|
+
const description = "No forbidden imports allowed";
|
|
5839
|
+
return [
|
|
5840
|
+
{
|
|
5841
|
+
id: constraintRuleId(this.category, "project", description),
|
|
5842
|
+
category: this.category,
|
|
5843
|
+
description,
|
|
5844
|
+
scope: "project"
|
|
5845
|
+
}
|
|
5846
|
+
];
|
|
5847
|
+
}
|
|
5848
|
+
async collect(_config, rootDir) {
|
|
5849
|
+
const stubParser = {
|
|
5850
|
+
name: "typescript",
|
|
5851
|
+
extensions: [".ts", ".tsx"],
|
|
5852
|
+
parseFile: async () => ({ ok: false, error: { code: "PARSE_ERROR", message: "" } }),
|
|
5853
|
+
extractImports: () => ({ ok: false, error: { code: "EXTRACT_ERROR", message: "" } }),
|
|
5854
|
+
extractExports: () => ({ ok: false, error: { code: "EXTRACT_ERROR", message: "" } }),
|
|
5855
|
+
health: async () => ({ ok: true, value: { available: true } })
|
|
5856
|
+
};
|
|
5857
|
+
const result = await validateDependencies({
|
|
5858
|
+
layers: [],
|
|
5859
|
+
rootDir,
|
|
5860
|
+
parser: stubParser,
|
|
5861
|
+
fallbackBehavior: "skip"
|
|
5862
|
+
});
|
|
5863
|
+
if (!result.ok) {
|
|
5864
|
+
return [
|
|
5865
|
+
{
|
|
5866
|
+
category: this.category,
|
|
5867
|
+
scope: "project",
|
|
5868
|
+
value: 0,
|
|
5869
|
+
violations: [],
|
|
5870
|
+
metadata: { error: "Failed to validate dependencies" }
|
|
5871
|
+
}
|
|
5872
|
+
];
|
|
5873
|
+
}
|
|
5874
|
+
const forbidden = result.value.violations.filter(
|
|
5875
|
+
(v) => v.reason === "FORBIDDEN_IMPORT"
|
|
5876
|
+
);
|
|
5877
|
+
const violations = forbidden.map((v) => {
|
|
5878
|
+
const relFile = (0, import_node_path7.relative)(rootDir, v.file);
|
|
5879
|
+
const relImport = (0, import_node_path7.relative)(rootDir, v.imports);
|
|
5880
|
+
const detail = `forbidden import: ${relFile} -> ${relImport}`;
|
|
5881
|
+
return {
|
|
5882
|
+
id: violationId(relFile, this.category, detail),
|
|
5883
|
+
file: relFile,
|
|
5884
|
+
category: this.category,
|
|
5885
|
+
detail,
|
|
5886
|
+
severity: "error"
|
|
5887
|
+
};
|
|
5888
|
+
});
|
|
5889
|
+
return [
|
|
5890
|
+
{
|
|
5891
|
+
category: this.category,
|
|
5892
|
+
scope: "project",
|
|
5893
|
+
value: violations.length,
|
|
5894
|
+
violations
|
|
5895
|
+
}
|
|
5896
|
+
];
|
|
5897
|
+
}
|
|
5898
|
+
};
|
|
5899
|
+
|
|
5900
|
+
// src/architecture/collectors/module-size.ts
|
|
5901
|
+
var import_promises2 = require("fs/promises");
|
|
5902
|
+
var import_node_path8 = require("path");
|
|
5903
|
+
async function discoverModules(rootDir) {
|
|
5904
|
+
const modules = [];
|
|
5905
|
+
async function scanDir(dir) {
|
|
5906
|
+
let entries;
|
|
5907
|
+
try {
|
|
5908
|
+
entries = await (0, import_promises2.readdir)(dir, { withFileTypes: true });
|
|
5909
|
+
} catch {
|
|
5910
|
+
return;
|
|
5911
|
+
}
|
|
5912
|
+
const tsFiles = [];
|
|
5913
|
+
const subdirs = [];
|
|
5914
|
+
for (const entry of entries) {
|
|
5915
|
+
if (entry.name.startsWith(".") || entry.name === "node_modules" || entry.name === "dist") {
|
|
5916
|
+
continue;
|
|
5917
|
+
}
|
|
5918
|
+
const fullPath = (0, import_node_path8.join)(dir, entry.name);
|
|
5919
|
+
if (entry.isDirectory()) {
|
|
5920
|
+
subdirs.push(fullPath);
|
|
5921
|
+
} 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")) {
|
|
5922
|
+
tsFiles.push(fullPath);
|
|
5923
|
+
}
|
|
5924
|
+
}
|
|
5925
|
+
if (tsFiles.length > 0) {
|
|
5926
|
+
let totalLoc = 0;
|
|
5927
|
+
for (const f of tsFiles) {
|
|
5928
|
+
try {
|
|
5929
|
+
const content = await (0, import_promises2.readFile)(f, "utf-8");
|
|
5930
|
+
totalLoc += content.split("\n").filter((line) => line.trim().length > 0).length;
|
|
5931
|
+
} catch {
|
|
5932
|
+
}
|
|
5933
|
+
}
|
|
5934
|
+
modules.push({
|
|
5935
|
+
modulePath: (0, import_node_path8.relative)(rootDir, dir),
|
|
5936
|
+
fileCount: tsFiles.length,
|
|
5937
|
+
totalLoc,
|
|
5938
|
+
files: tsFiles.map((f) => (0, import_node_path8.relative)(rootDir, f))
|
|
5939
|
+
});
|
|
5940
|
+
}
|
|
5941
|
+
for (const sub of subdirs) {
|
|
5942
|
+
await scanDir(sub);
|
|
5943
|
+
}
|
|
5944
|
+
}
|
|
5945
|
+
await scanDir(rootDir);
|
|
5946
|
+
return modules;
|
|
5947
|
+
}
|
|
5948
|
+
var ModuleSizeCollector = class {
|
|
5949
|
+
category = "module-size";
|
|
5950
|
+
getRules(config, _rootDir) {
|
|
5951
|
+
const thresholds = config.thresholds["module-size"];
|
|
5952
|
+
let maxLoc = Infinity;
|
|
5953
|
+
let maxFiles = Infinity;
|
|
5954
|
+
if (typeof thresholds === "object" && thresholds !== null) {
|
|
5955
|
+
const t = thresholds;
|
|
5956
|
+
if (t.maxLoc !== void 0) maxLoc = t.maxLoc;
|
|
5957
|
+
if (t.maxFiles !== void 0) maxFiles = t.maxFiles;
|
|
5958
|
+
}
|
|
5959
|
+
const rules = [];
|
|
5960
|
+
if (maxLoc < Infinity) {
|
|
5961
|
+
const desc = `Module LOC must not exceed ${maxLoc}`;
|
|
5962
|
+
rules.push({
|
|
5963
|
+
id: constraintRuleId(this.category, "project", desc),
|
|
5964
|
+
category: this.category,
|
|
5965
|
+
description: desc,
|
|
5966
|
+
scope: "project"
|
|
5967
|
+
});
|
|
5968
|
+
}
|
|
5969
|
+
if (maxFiles < Infinity) {
|
|
5970
|
+
const desc = `Module file count must not exceed ${maxFiles}`;
|
|
5971
|
+
rules.push({
|
|
5972
|
+
id: constraintRuleId(this.category, "project", desc),
|
|
5973
|
+
category: this.category,
|
|
5974
|
+
description: desc,
|
|
5975
|
+
scope: "project"
|
|
5976
|
+
});
|
|
5977
|
+
}
|
|
5978
|
+
if (rules.length === 0) {
|
|
5979
|
+
const desc = "Module size must stay within thresholds";
|
|
5980
|
+
rules.push({
|
|
5981
|
+
id: constraintRuleId(this.category, "project", desc),
|
|
5982
|
+
category: this.category,
|
|
5983
|
+
description: desc,
|
|
5984
|
+
scope: "project"
|
|
5985
|
+
});
|
|
5986
|
+
}
|
|
5987
|
+
return rules;
|
|
5988
|
+
}
|
|
5989
|
+
async collect(config, rootDir) {
|
|
5990
|
+
const modules = await discoverModules(rootDir);
|
|
5991
|
+
const thresholds = config.thresholds["module-size"];
|
|
5992
|
+
let maxLoc = Infinity;
|
|
5993
|
+
let maxFiles = Infinity;
|
|
5994
|
+
if (typeof thresholds === "object" && thresholds !== null) {
|
|
5995
|
+
const t = thresholds;
|
|
5996
|
+
if (t.maxLoc !== void 0) maxLoc = t.maxLoc;
|
|
5997
|
+
if (t.maxFiles !== void 0) maxFiles = t.maxFiles;
|
|
5998
|
+
}
|
|
5999
|
+
return modules.map((mod) => {
|
|
6000
|
+
const violations = [];
|
|
6001
|
+
if (mod.totalLoc > maxLoc) {
|
|
6002
|
+
violations.push({
|
|
6003
|
+
id: violationId(mod.modulePath, this.category, "totalLoc-exceeded"),
|
|
6004
|
+
file: mod.modulePath,
|
|
6005
|
+
detail: `Module has ${mod.totalLoc} lines of code (threshold: ${maxLoc})`,
|
|
6006
|
+
severity: "warning"
|
|
6007
|
+
});
|
|
6008
|
+
}
|
|
6009
|
+
if (mod.fileCount > maxFiles) {
|
|
6010
|
+
violations.push({
|
|
6011
|
+
id: violationId(mod.modulePath, this.category, "fileCount-exceeded"),
|
|
6012
|
+
file: mod.modulePath,
|
|
6013
|
+
detail: `Module has ${mod.fileCount} files (threshold: ${maxFiles})`,
|
|
6014
|
+
severity: "warning"
|
|
6015
|
+
});
|
|
6016
|
+
}
|
|
6017
|
+
return {
|
|
6018
|
+
category: this.category,
|
|
6019
|
+
scope: mod.modulePath,
|
|
6020
|
+
value: mod.totalLoc,
|
|
6021
|
+
violations,
|
|
6022
|
+
metadata: { fileCount: mod.fileCount, totalLoc: mod.totalLoc }
|
|
6023
|
+
};
|
|
6024
|
+
});
|
|
6025
|
+
}
|
|
6026
|
+
};
|
|
6027
|
+
|
|
6028
|
+
// src/architecture/collectors/dep-depth.ts
|
|
6029
|
+
var import_promises3 = require("fs/promises");
|
|
6030
|
+
var import_node_path9 = require("path");
|
|
6031
|
+
function extractImportSources(content, filePath) {
|
|
6032
|
+
const importRegex = /(?:import|export)\s+.*?from\s+['"](\.[^'"]+)['"]/g;
|
|
6033
|
+
const dynamicRegex = /import\s*\(\s*['"](\.[^'"]+)['"]\s*\)/g;
|
|
6034
|
+
const sources = [];
|
|
6035
|
+
const dir = (0, import_node_path9.dirname)(filePath);
|
|
6036
|
+
for (const regex of [importRegex, dynamicRegex]) {
|
|
6037
|
+
let match;
|
|
6038
|
+
while ((match = regex.exec(content)) !== null) {
|
|
6039
|
+
let resolved = (0, import_node_path9.resolve)(dir, match[1]);
|
|
6040
|
+
if (!resolved.endsWith(".ts") && !resolved.endsWith(".tsx")) {
|
|
6041
|
+
resolved += ".ts";
|
|
6042
|
+
}
|
|
6043
|
+
sources.push(resolved);
|
|
6044
|
+
}
|
|
6045
|
+
}
|
|
6046
|
+
return sources;
|
|
6047
|
+
}
|
|
6048
|
+
async function collectTsFiles(dir) {
|
|
6049
|
+
const results = [];
|
|
6050
|
+
async function scan(d) {
|
|
6051
|
+
let entries;
|
|
6052
|
+
try {
|
|
6053
|
+
entries = await (0, import_promises3.readdir)(d, { withFileTypes: true });
|
|
6054
|
+
} catch {
|
|
6055
|
+
return;
|
|
6056
|
+
}
|
|
6057
|
+
for (const entry of entries) {
|
|
6058
|
+
if (entry.name.startsWith(".") || entry.name === "node_modules" || entry.name === "dist")
|
|
6059
|
+
continue;
|
|
6060
|
+
const fullPath = (0, import_node_path9.join)(d, entry.name);
|
|
6061
|
+
if (entry.isDirectory()) {
|
|
6062
|
+
await scan(fullPath);
|
|
6063
|
+
} 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")) {
|
|
6064
|
+
results.push(fullPath);
|
|
6065
|
+
}
|
|
6066
|
+
}
|
|
6067
|
+
}
|
|
6068
|
+
await scan(dir);
|
|
6069
|
+
return results;
|
|
6070
|
+
}
|
|
6071
|
+
function computeLongestChain(file, graph, visited, memo) {
|
|
6072
|
+
if (memo.has(file)) return memo.get(file);
|
|
6073
|
+
if (visited.has(file)) return 0;
|
|
6074
|
+
visited.add(file);
|
|
6075
|
+
const deps = graph.get(file) || [];
|
|
6076
|
+
let maxDepth = 0;
|
|
6077
|
+
for (const dep of deps) {
|
|
6078
|
+
const depth = 1 + computeLongestChain(dep, graph, visited, memo);
|
|
6079
|
+
if (depth > maxDepth) maxDepth = depth;
|
|
6080
|
+
}
|
|
6081
|
+
visited.delete(file);
|
|
6082
|
+
memo.set(file, maxDepth);
|
|
6083
|
+
return maxDepth;
|
|
6084
|
+
}
|
|
6085
|
+
var DepDepthCollector = class {
|
|
6086
|
+
category = "dependency-depth";
|
|
6087
|
+
getRules(config, _rootDir) {
|
|
6088
|
+
const threshold = typeof config.thresholds["dependency-depth"] === "number" ? config.thresholds["dependency-depth"] : null;
|
|
6089
|
+
const desc = threshold !== null ? `Dependency chain depth must not exceed ${threshold}` : "Dependency chain depth must stay within thresholds";
|
|
6090
|
+
return [
|
|
6091
|
+
{
|
|
6092
|
+
id: constraintRuleId(this.category, "project", desc),
|
|
6093
|
+
category: this.category,
|
|
6094
|
+
description: desc,
|
|
6095
|
+
scope: "project"
|
|
6096
|
+
}
|
|
6097
|
+
];
|
|
6098
|
+
}
|
|
6099
|
+
async collect(config, rootDir) {
|
|
6100
|
+
const allFiles = await collectTsFiles(rootDir);
|
|
6101
|
+
const graph = /* @__PURE__ */ new Map();
|
|
6102
|
+
const fileSet = new Set(allFiles);
|
|
6103
|
+
for (const file of allFiles) {
|
|
6104
|
+
try {
|
|
6105
|
+
const content = await (0, import_promises3.readFile)(file, "utf-8");
|
|
6106
|
+
const imports = extractImportSources(content, file).filter((imp) => fileSet.has(imp));
|
|
6107
|
+
graph.set(file, imports);
|
|
6108
|
+
} catch {
|
|
6109
|
+
graph.set(file, []);
|
|
6110
|
+
}
|
|
6111
|
+
}
|
|
6112
|
+
const moduleMap = /* @__PURE__ */ new Map();
|
|
6113
|
+
for (const file of allFiles) {
|
|
6114
|
+
const relDir = (0, import_node_path9.relative)(rootDir, (0, import_node_path9.dirname)(file));
|
|
6115
|
+
if (!moduleMap.has(relDir)) moduleMap.set(relDir, []);
|
|
6116
|
+
moduleMap.get(relDir).push(file);
|
|
6117
|
+
}
|
|
6118
|
+
const memo = /* @__PURE__ */ new Map();
|
|
6119
|
+
const threshold = typeof config.thresholds["dependency-depth"] === "number" ? config.thresholds["dependency-depth"] : Infinity;
|
|
6120
|
+
const results = [];
|
|
6121
|
+
for (const [modulePath, files] of moduleMap) {
|
|
6122
|
+
let longestChain = 0;
|
|
6123
|
+
for (const file of files) {
|
|
6124
|
+
const depth = computeLongestChain(file, graph, /* @__PURE__ */ new Set(), memo);
|
|
6125
|
+
if (depth > longestChain) longestChain = depth;
|
|
6126
|
+
}
|
|
6127
|
+
const violations = [];
|
|
6128
|
+
if (longestChain > threshold) {
|
|
6129
|
+
violations.push({
|
|
6130
|
+
id: violationId(modulePath, this.category, "depth-exceeded"),
|
|
6131
|
+
file: modulePath,
|
|
6132
|
+
detail: `Import chain depth is ${longestChain} (threshold: ${threshold})`,
|
|
6133
|
+
severity: "warning"
|
|
6134
|
+
});
|
|
6135
|
+
}
|
|
6136
|
+
results.push({
|
|
6137
|
+
category: this.category,
|
|
6138
|
+
scope: modulePath,
|
|
6139
|
+
value: longestChain,
|
|
6140
|
+
violations,
|
|
6141
|
+
metadata: { longestChain }
|
|
6142
|
+
});
|
|
6143
|
+
}
|
|
6144
|
+
return results;
|
|
6145
|
+
}
|
|
6146
|
+
};
|
|
6147
|
+
|
|
6148
|
+
// src/architecture/collectors/index.ts
|
|
6149
|
+
var defaultCollectors = [
|
|
6150
|
+
new CircularDepsCollector(),
|
|
6151
|
+
new LayerViolationCollector(),
|
|
6152
|
+
new ComplexityCollector(),
|
|
6153
|
+
new CouplingCollector(),
|
|
6154
|
+
new ForbiddenImportCollector(),
|
|
6155
|
+
new ModuleSizeCollector(),
|
|
6156
|
+
new DepDepthCollector()
|
|
6157
|
+
];
|
|
6158
|
+
async function runAll(config, rootDir, collectors = defaultCollectors) {
|
|
6159
|
+
const results = await Promise.allSettled(collectors.map((c) => c.collect(config, rootDir)));
|
|
6160
|
+
const allResults = [];
|
|
6161
|
+
for (let i = 0; i < results.length; i++) {
|
|
6162
|
+
const result = results[i];
|
|
6163
|
+
if (result.status === "fulfilled") {
|
|
6164
|
+
allResults.push(...result.value);
|
|
6165
|
+
} else {
|
|
6166
|
+
allResults.push({
|
|
6167
|
+
category: collectors[i].category,
|
|
6168
|
+
scope: "project",
|
|
6169
|
+
value: 0,
|
|
6170
|
+
violations: [],
|
|
6171
|
+
metadata: { error: String(result.reason) }
|
|
6172
|
+
});
|
|
6173
|
+
}
|
|
6174
|
+
}
|
|
6175
|
+
return allResults;
|
|
6176
|
+
}
|
|
6177
|
+
|
|
6178
|
+
// src/architecture/sync-constraints.ts
|
|
6179
|
+
function syncConstraintNodes(store, rules, violations) {
|
|
6180
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
6181
|
+
const ruleIds = new Set(rules.map((r) => r.id));
|
|
6182
|
+
const violationsByCategory = /* @__PURE__ */ new Map();
|
|
6183
|
+
for (const result of violations) {
|
|
6184
|
+
const files = result.violations.map((v) => v.file);
|
|
6185
|
+
const existing = violationsByCategory.get(result.category) ?? [];
|
|
6186
|
+
violationsByCategory.set(result.category, [...existing, ...files]);
|
|
6187
|
+
}
|
|
6188
|
+
const existingNodesById = /* @__PURE__ */ new Map();
|
|
6189
|
+
for (const node of store.findNodes({ type: "constraint" })) {
|
|
6190
|
+
existingNodesById.set(node.id, node);
|
|
6191
|
+
}
|
|
6192
|
+
for (const rule of rules) {
|
|
6193
|
+
const existing = existingNodesById.get(rule.id);
|
|
6194
|
+
const createdAt = existing?.createdAt ?? now;
|
|
6195
|
+
const previousLastViolatedAt = existing?.lastViolatedAt ?? null;
|
|
6196
|
+
const hasViolation = hasMatchingViolation(rule, violationsByCategory);
|
|
6197
|
+
const lastViolatedAt = hasViolation ? now : previousLastViolatedAt;
|
|
6198
|
+
store.upsertNode({
|
|
6199
|
+
id: rule.id,
|
|
6200
|
+
type: "constraint",
|
|
6201
|
+
name: rule.description,
|
|
6202
|
+
category: rule.category,
|
|
6203
|
+
scope: rule.scope,
|
|
6204
|
+
createdAt,
|
|
6205
|
+
lastViolatedAt
|
|
6206
|
+
});
|
|
6207
|
+
}
|
|
6208
|
+
const existingConstraints = store.findNodes({ type: "constraint" });
|
|
6209
|
+
for (const node of existingConstraints) {
|
|
6210
|
+
if (!ruleIds.has(node.id)) {
|
|
6211
|
+
store.removeNode(node.id);
|
|
6212
|
+
}
|
|
6213
|
+
}
|
|
6214
|
+
}
|
|
6215
|
+
function hasMatchingViolation(rule, violationsByCategory) {
|
|
6216
|
+
const files = violationsByCategory.get(rule.category);
|
|
6217
|
+
if (!files || files.length === 0) return false;
|
|
6218
|
+
if (rule.scope === "project") return true;
|
|
6219
|
+
return files.some((file) => file.startsWith(rule.scope));
|
|
6220
|
+
}
|
|
6221
|
+
|
|
6222
|
+
// src/architecture/detect-stale.ts
|
|
6223
|
+
function detectStaleConstraints(store, windowDays = 30, category) {
|
|
6224
|
+
const now = Date.now();
|
|
6225
|
+
const windowMs = windowDays * 24 * 60 * 60 * 1e3;
|
|
6226
|
+
const cutoff = now - windowMs;
|
|
6227
|
+
let constraints = store.findNodes({ type: "constraint" });
|
|
6228
|
+
if (category) {
|
|
6229
|
+
constraints = constraints.filter((n) => n.category === category);
|
|
6230
|
+
}
|
|
6231
|
+
const totalConstraints = constraints.length;
|
|
6232
|
+
const staleConstraints = [];
|
|
6233
|
+
for (const node of constraints) {
|
|
6234
|
+
const lastViolatedAt = node.lastViolatedAt ?? null;
|
|
6235
|
+
const createdAt = node.createdAt;
|
|
6236
|
+
const comparisonTimestamp = lastViolatedAt ?? createdAt;
|
|
6237
|
+
if (!comparisonTimestamp) continue;
|
|
6238
|
+
const timestampMs = new Date(comparisonTimestamp).getTime();
|
|
6239
|
+
if (timestampMs < cutoff) {
|
|
6240
|
+
const daysSince = Math.floor((now - timestampMs) / (24 * 60 * 60 * 1e3));
|
|
6241
|
+
staleConstraints.push({
|
|
6242
|
+
id: node.id,
|
|
6243
|
+
category: node.category,
|
|
6244
|
+
description: node.name ?? "",
|
|
6245
|
+
scope: node.scope ?? "project",
|
|
6246
|
+
lastViolatedAt,
|
|
6247
|
+
daysSinceLastViolation: daysSince
|
|
6248
|
+
});
|
|
6249
|
+
}
|
|
6250
|
+
}
|
|
6251
|
+
staleConstraints.sort((a, b) => b.daysSinceLastViolation - a.daysSinceLastViolation);
|
|
6252
|
+
return { staleConstraints, totalConstraints, windowDays };
|
|
6253
|
+
}
|
|
6254
|
+
|
|
6255
|
+
// src/architecture/baseline-manager.ts
|
|
6256
|
+
var import_node_fs3 = require("fs");
|
|
6257
|
+
var import_node_crypto2 = require("crypto");
|
|
6258
|
+
var import_node_path10 = require("path");
|
|
6259
|
+
var ArchBaselineManager = class {
|
|
6260
|
+
baselinesPath;
|
|
6261
|
+
constructor(projectRoot, baselinePath) {
|
|
6262
|
+
this.baselinesPath = baselinePath ? (0, import_node_path10.join)(projectRoot, baselinePath) : (0, import_node_path10.join)(projectRoot, ".harness", "arch", "baselines.json");
|
|
6263
|
+
}
|
|
6264
|
+
/**
|
|
6265
|
+
* Snapshot the current metric results into an ArchBaseline.
|
|
6266
|
+
* Aggregates multiple MetricResults for the same category by summing values
|
|
6267
|
+
* and concatenating violation IDs.
|
|
6268
|
+
*/
|
|
6269
|
+
capture(results, commitHash) {
|
|
6270
|
+
const metrics = {};
|
|
6271
|
+
for (const result of results) {
|
|
6272
|
+
const existing = metrics[result.category];
|
|
6273
|
+
if (existing) {
|
|
6274
|
+
existing.value += result.value;
|
|
6275
|
+
existing.violationIds.push(...result.violations.map((v) => v.id));
|
|
6276
|
+
} else {
|
|
6277
|
+
metrics[result.category] = {
|
|
6278
|
+
value: result.value,
|
|
6279
|
+
violationIds: result.violations.map((v) => v.id)
|
|
6280
|
+
};
|
|
6281
|
+
}
|
|
6282
|
+
}
|
|
6283
|
+
return {
|
|
6284
|
+
version: 1,
|
|
6285
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
6286
|
+
updatedFrom: commitHash,
|
|
6287
|
+
metrics
|
|
6288
|
+
};
|
|
6289
|
+
}
|
|
6290
|
+
/**
|
|
6291
|
+
* Load the baselines file from disk.
|
|
6292
|
+
* Returns null if the file does not exist, contains invalid JSON,
|
|
6293
|
+
* or fails ArchBaselineSchema validation.
|
|
6294
|
+
*/
|
|
6295
|
+
load() {
|
|
6296
|
+
if (!(0, import_node_fs3.existsSync)(this.baselinesPath)) {
|
|
6297
|
+
console.error(`Baseline file not found at: ${this.baselinesPath}`);
|
|
6298
|
+
return null;
|
|
6299
|
+
}
|
|
6300
|
+
try {
|
|
6301
|
+
const raw = (0, import_node_fs3.readFileSync)(this.baselinesPath, "utf-8");
|
|
6302
|
+
const data = JSON.parse(raw);
|
|
6303
|
+
const parsed = ArchBaselineSchema.safeParse(data);
|
|
6304
|
+
if (!parsed.success) {
|
|
6305
|
+
console.error(
|
|
6306
|
+
`Baseline validation failed for ${this.baselinesPath}:`,
|
|
6307
|
+
parsed.error.format()
|
|
6308
|
+
);
|
|
6309
|
+
return null;
|
|
6310
|
+
}
|
|
6311
|
+
return parsed.data;
|
|
6312
|
+
} catch (error) {
|
|
6313
|
+
console.error(`Error loading baseline from ${this.baselinesPath}:`, error);
|
|
6314
|
+
return null;
|
|
6315
|
+
}
|
|
6316
|
+
}
|
|
6317
|
+
/**
|
|
6318
|
+
* Save an ArchBaseline to disk.
|
|
6319
|
+
* Creates parent directories if they do not exist.
|
|
6320
|
+
* Uses atomic write (write to temp file, then rename) to prevent corruption.
|
|
6321
|
+
*/
|
|
6322
|
+
save(baseline) {
|
|
6323
|
+
const dir = (0, import_node_path10.dirname)(this.baselinesPath);
|
|
6324
|
+
if (!(0, import_node_fs3.existsSync)(dir)) {
|
|
6325
|
+
(0, import_node_fs3.mkdirSync)(dir, { recursive: true });
|
|
6326
|
+
}
|
|
6327
|
+
const tmp = this.baselinesPath + "." + (0, import_node_crypto2.randomBytes)(4).toString("hex") + ".tmp";
|
|
6328
|
+
(0, import_node_fs3.writeFileSync)(tmp, JSON.stringify(baseline, null, 2));
|
|
6329
|
+
(0, import_node_fs3.renameSync)(tmp, this.baselinesPath);
|
|
6330
|
+
}
|
|
6331
|
+
};
|
|
6332
|
+
|
|
6333
|
+
// src/architecture/diff.ts
|
|
6334
|
+
function aggregateByCategory(results) {
|
|
6335
|
+
const map = /* @__PURE__ */ new Map();
|
|
6336
|
+
for (const result of results) {
|
|
6337
|
+
const existing = map.get(result.category);
|
|
6338
|
+
if (existing) {
|
|
6339
|
+
existing.value += result.value;
|
|
6340
|
+
existing.violations.push(...result.violations);
|
|
6341
|
+
} else {
|
|
6342
|
+
map.set(result.category, {
|
|
6343
|
+
value: result.value,
|
|
6344
|
+
violations: [...result.violations]
|
|
6345
|
+
});
|
|
6346
|
+
}
|
|
6347
|
+
}
|
|
6348
|
+
return map;
|
|
6349
|
+
}
|
|
6350
|
+
function diff(current, baseline) {
|
|
6351
|
+
const aggregated = aggregateByCategory(current);
|
|
6352
|
+
const newViolations = [];
|
|
6353
|
+
const resolvedViolations = [];
|
|
6354
|
+
const preExisting = [];
|
|
6355
|
+
const regressions = [];
|
|
6356
|
+
const visitedCategories = /* @__PURE__ */ new Set();
|
|
6357
|
+
for (const [category, agg] of aggregated) {
|
|
6358
|
+
visitedCategories.add(category);
|
|
6359
|
+
const baselineCategory = baseline.metrics[category];
|
|
6360
|
+
const baselineViolationIds = new Set(baselineCategory?.violationIds ?? []);
|
|
6361
|
+
const baselineValue = baselineCategory?.value ?? 0;
|
|
6362
|
+
for (const violation of agg.violations) {
|
|
6363
|
+
if (baselineViolationIds.has(violation.id)) {
|
|
6364
|
+
preExisting.push(violation.id);
|
|
6365
|
+
} else {
|
|
6366
|
+
newViolations.push(violation);
|
|
6367
|
+
}
|
|
6368
|
+
}
|
|
6369
|
+
const currentViolationIds = new Set(agg.violations.map((v) => v.id));
|
|
6370
|
+
if (baselineCategory) {
|
|
6371
|
+
for (const id of baselineCategory.violationIds) {
|
|
6372
|
+
if (!currentViolationIds.has(id)) {
|
|
6373
|
+
resolvedViolations.push(id);
|
|
6374
|
+
}
|
|
6375
|
+
}
|
|
6376
|
+
}
|
|
6377
|
+
if (baselineCategory && agg.value > baselineValue) {
|
|
6378
|
+
regressions.push({
|
|
6379
|
+
category,
|
|
6380
|
+
baselineValue,
|
|
6381
|
+
currentValue: agg.value,
|
|
6382
|
+
delta: agg.value - baselineValue
|
|
6383
|
+
});
|
|
6384
|
+
}
|
|
6385
|
+
}
|
|
6386
|
+
for (const [category, baselineCategory] of Object.entries(baseline.metrics)) {
|
|
6387
|
+
if (!visitedCategories.has(category) && baselineCategory) {
|
|
6388
|
+
for (const id of baselineCategory.violationIds) {
|
|
6389
|
+
resolvedViolations.push(id);
|
|
6390
|
+
}
|
|
6391
|
+
}
|
|
6392
|
+
}
|
|
6393
|
+
const passed = newViolations.length === 0 && regressions.length === 0;
|
|
6394
|
+
return {
|
|
6395
|
+
passed,
|
|
6396
|
+
newViolations,
|
|
6397
|
+
resolvedViolations,
|
|
6398
|
+
preExisting,
|
|
6399
|
+
regressions
|
|
6400
|
+
};
|
|
6401
|
+
}
|
|
6402
|
+
|
|
6403
|
+
// src/architecture/config.ts
|
|
6404
|
+
function resolveThresholds(scope, config) {
|
|
6405
|
+
const projectThresholds = {};
|
|
6406
|
+
for (const [key, val] of Object.entries(config.thresholds)) {
|
|
6407
|
+
projectThresholds[key] = typeof val === "object" && val !== null && !Array.isArray(val) ? { ...val } : val;
|
|
6408
|
+
}
|
|
6409
|
+
if (scope === "project") {
|
|
6410
|
+
return projectThresholds;
|
|
6411
|
+
}
|
|
6412
|
+
const moduleOverrides = config.modules[scope];
|
|
6413
|
+
if (!moduleOverrides) {
|
|
6414
|
+
return projectThresholds;
|
|
6415
|
+
}
|
|
6416
|
+
const merged = { ...projectThresholds };
|
|
6417
|
+
for (const [category, moduleValue] of Object.entries(moduleOverrides)) {
|
|
6418
|
+
const projectValue = projectThresholds[category];
|
|
6419
|
+
if (projectValue !== void 0 && typeof projectValue === "object" && !Array.isArray(projectValue) && typeof moduleValue === "object" && !Array.isArray(moduleValue)) {
|
|
6420
|
+
merged[category] = {
|
|
6421
|
+
...projectValue,
|
|
6422
|
+
...moduleValue
|
|
6423
|
+
};
|
|
6424
|
+
} else {
|
|
6425
|
+
merged[category] = moduleValue;
|
|
6426
|
+
}
|
|
6427
|
+
}
|
|
6428
|
+
return merged;
|
|
6429
|
+
}
|
|
6430
|
+
|
|
6431
|
+
// src/architecture/matchers.ts
|
|
6432
|
+
function architecture(options) {
|
|
6433
|
+
return {
|
|
6434
|
+
kind: "arch-handle",
|
|
6435
|
+
scope: "project",
|
|
6436
|
+
rootDir: options?.rootDir ?? process.cwd(),
|
|
6437
|
+
config: options?.config
|
|
6438
|
+
};
|
|
6439
|
+
}
|
|
6440
|
+
function archModule(modulePath, options) {
|
|
6441
|
+
return {
|
|
6442
|
+
kind: "arch-handle",
|
|
6443
|
+
scope: modulePath,
|
|
6444
|
+
rootDir: options?.rootDir ?? process.cwd(),
|
|
6445
|
+
config: options?.config
|
|
6446
|
+
};
|
|
6447
|
+
}
|
|
6448
|
+
function resolveConfig(handle) {
|
|
6449
|
+
return ArchConfigSchema.parse(handle.config ?? {});
|
|
6450
|
+
}
|
|
6451
|
+
async function collectCategory(handle, collector) {
|
|
6452
|
+
if ("_mockResults" in handle && handle._mockResults) {
|
|
6453
|
+
return handle._mockResults;
|
|
6454
|
+
}
|
|
6455
|
+
const config = resolveConfig(handle);
|
|
6456
|
+
return collector.collect(config, handle.rootDir);
|
|
6457
|
+
}
|
|
6458
|
+
function formatViolationList(violations, limit = 10) {
|
|
6459
|
+
const lines = violations.slice(0, limit).map((v) => ` - ${v.file}: ${v.detail}`);
|
|
6460
|
+
if (violations.length > limit) {
|
|
6461
|
+
lines.push(` ... and ${violations.length - limit} more`);
|
|
6462
|
+
}
|
|
6463
|
+
return lines.join("\n");
|
|
6464
|
+
}
|
|
6465
|
+
async function toHaveNoCircularDeps(received) {
|
|
6466
|
+
const results = await collectCategory(received, new CircularDepsCollector());
|
|
6467
|
+
const violations = results.flatMap((r) => r.violations);
|
|
6468
|
+
const pass = violations.length === 0;
|
|
6469
|
+
return {
|
|
6470
|
+
pass,
|
|
6471
|
+
message: () => pass ? "Expected circular dependencies but found none" : `Found ${violations.length} circular dependenc${violations.length === 1 ? "y" : "ies"}:
|
|
6472
|
+
${formatViolationList(violations)}`
|
|
6473
|
+
};
|
|
6474
|
+
}
|
|
6475
|
+
async function toHaveNoLayerViolations(received) {
|
|
6476
|
+
const results = await collectCategory(received, new LayerViolationCollector());
|
|
6477
|
+
const violations = results.flatMap((r) => r.violations);
|
|
6478
|
+
const pass = violations.length === 0;
|
|
6479
|
+
return {
|
|
6480
|
+
pass,
|
|
6481
|
+
message: () => pass ? "Expected layer violations but found none" : `Found ${violations.length} layer violation${violations.length === 1 ? "" : "s"}:
|
|
6482
|
+
${formatViolationList(violations)}`
|
|
6483
|
+
};
|
|
6484
|
+
}
|
|
6485
|
+
async function toMatchBaseline(received, options) {
|
|
6486
|
+
let diffResult;
|
|
6487
|
+
if ("_mockDiff" in received && received._mockDiff) {
|
|
6488
|
+
diffResult = received._mockDiff;
|
|
6489
|
+
} else {
|
|
6490
|
+
const config = resolveConfig(received);
|
|
6491
|
+
const results = await runAll(config, received.rootDir);
|
|
6492
|
+
const manager = new ArchBaselineManager(received.rootDir, config.baselinePath);
|
|
6493
|
+
const baseline = manager.load();
|
|
6494
|
+
if (!baseline) {
|
|
6495
|
+
return {
|
|
6496
|
+
pass: false,
|
|
6497
|
+
message: () => "No baseline found. Run `harness check-arch --update-baseline` to create one."
|
|
6498
|
+
};
|
|
6499
|
+
}
|
|
6500
|
+
diffResult = diff(results, baseline);
|
|
6501
|
+
}
|
|
6502
|
+
const tolerance = options?.tolerance ?? 0;
|
|
6503
|
+
const effectiveNewCount = Math.max(0, diffResult.newViolations.length - tolerance);
|
|
6504
|
+
const pass = effectiveNewCount === 0 && diffResult.regressions.length === 0;
|
|
6505
|
+
return {
|
|
6506
|
+
pass,
|
|
6507
|
+
message: () => {
|
|
6508
|
+
if (pass) {
|
|
6509
|
+
return "Expected baseline regression but architecture matches baseline";
|
|
6510
|
+
}
|
|
6511
|
+
const parts = [];
|
|
6512
|
+
if (diffResult.newViolations.length > 0) {
|
|
6513
|
+
parts.push(
|
|
6514
|
+
`${diffResult.newViolations.length} new violation${diffResult.newViolations.length === 1 ? "" : "s"}${tolerance > 0 ? ` (tolerance: ${tolerance})` : ""}:
|
|
6515
|
+
${formatViolationList(diffResult.newViolations)}`
|
|
6516
|
+
);
|
|
6517
|
+
}
|
|
6518
|
+
if (diffResult.regressions.length > 0) {
|
|
6519
|
+
const regLines = diffResult.regressions.map(
|
|
6520
|
+
(r) => ` - ${r.category}: ${r.baselineValue} -> ${r.currentValue} (+${r.delta})`
|
|
6521
|
+
);
|
|
6522
|
+
parts.push(`Regressions:
|
|
6523
|
+
${regLines.join("\n")}`);
|
|
6524
|
+
}
|
|
6525
|
+
return `Baseline check failed:
|
|
6526
|
+
${parts.join("\n\n")}`;
|
|
6527
|
+
}
|
|
6528
|
+
};
|
|
6529
|
+
}
|
|
6530
|
+
function filterByScope(results, scope) {
|
|
6531
|
+
return results.filter(
|
|
6532
|
+
(r) => r.scope === scope || r.scope.startsWith(scope + "/") || r.scope === "project"
|
|
6533
|
+
);
|
|
6534
|
+
}
|
|
6535
|
+
async function toHaveMaxComplexity(received, maxComplexity) {
|
|
6536
|
+
const results = await collectCategory(received, new ComplexityCollector());
|
|
6537
|
+
const scoped = filterByScope(results, received.scope);
|
|
6538
|
+
const violations = scoped.flatMap((r) => r.violations);
|
|
6539
|
+
const totalValue = scoped.reduce((sum, r) => sum + r.value, 0);
|
|
6540
|
+
const pass = totalValue <= maxComplexity && violations.length === 0;
|
|
6541
|
+
return {
|
|
6542
|
+
pass,
|
|
6543
|
+
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"}):
|
|
6544
|
+
${formatViolationList(violations)}`
|
|
6545
|
+
};
|
|
6546
|
+
}
|
|
6547
|
+
async function toHaveMaxCoupling(received, limits) {
|
|
6548
|
+
const config = resolveConfig(received);
|
|
6549
|
+
if (limits.fanIn !== void 0 || limits.fanOut !== void 0) {
|
|
6550
|
+
config.thresholds.coupling = {
|
|
6551
|
+
...typeof config.thresholds.coupling === "object" ? config.thresholds.coupling : {},
|
|
6552
|
+
...limits.fanIn !== void 0 ? { maxFanIn: limits.fanIn } : {},
|
|
6553
|
+
...limits.fanOut !== void 0 ? { maxFanOut: limits.fanOut } : {}
|
|
6554
|
+
};
|
|
6555
|
+
}
|
|
6556
|
+
const collector = new CouplingCollector();
|
|
6557
|
+
const results = "_mockResults" in received && received._mockResults ? received._mockResults : await collector.collect(config, received.rootDir);
|
|
6558
|
+
const scoped = filterByScope(results, received.scope);
|
|
6559
|
+
const violations = scoped.flatMap((r) => r.violations);
|
|
6560
|
+
const pass = violations.length === 0;
|
|
6561
|
+
return {
|
|
6562
|
+
pass,
|
|
6563
|
+
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"}):
|
|
6564
|
+
${formatViolationList(violations)}`
|
|
6565
|
+
};
|
|
6566
|
+
}
|
|
6567
|
+
async function toHaveMaxFileCount(received, maxFiles) {
|
|
6568
|
+
const results = await collectCategory(received, new ModuleSizeCollector());
|
|
6569
|
+
const scoped = filterByScope(results, received.scope);
|
|
6570
|
+
const fileCount = scoped.reduce((max, r) => {
|
|
6571
|
+
const meta = r.metadata;
|
|
6572
|
+
const fc = typeof meta?.fileCount === "number" ? meta.fileCount : 0;
|
|
6573
|
+
return fc > max ? fc : max;
|
|
6574
|
+
}, 0);
|
|
6575
|
+
const pass = fileCount <= maxFiles;
|
|
6576
|
+
return {
|
|
6577
|
+
pass,
|
|
6578
|
+
message: () => pass ? `Expected file count in '${received.scope}' to exceed ${maxFiles} but it was ${fileCount}` : `Module '${received.scope}' has ${fileCount} files (limit: ${maxFiles})`
|
|
6579
|
+
};
|
|
6580
|
+
}
|
|
6581
|
+
async function toNotDependOn(received, forbiddenModule) {
|
|
6582
|
+
const results = await collectCategory(received, new ForbiddenImportCollector());
|
|
6583
|
+
const allViolations = results.flatMap((r) => r.violations);
|
|
6584
|
+
const scopePrefix = received.scope.replace(/\/+$/, "");
|
|
6585
|
+
const forbiddenPrefix = forbiddenModule.replace(/\/+$/, "");
|
|
6586
|
+
const relevantViolations = allViolations.filter(
|
|
6587
|
+
(v) => (v.file === scopePrefix || v.file.startsWith(scopePrefix + "/")) && (v.detail.includes(forbiddenPrefix + "/") || v.detail.endsWith(forbiddenPrefix))
|
|
6588
|
+
);
|
|
6589
|
+
const pass = relevantViolations.length === 0;
|
|
6590
|
+
return {
|
|
6591
|
+
pass,
|
|
6592
|
+
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"}):
|
|
6593
|
+
${formatViolationList(relevantViolations)}`
|
|
6594
|
+
};
|
|
6595
|
+
}
|
|
6596
|
+
async function toHaveMaxDepDepth(received, maxDepth) {
|
|
6597
|
+
const results = await collectCategory(received, new DepDepthCollector());
|
|
6598
|
+
const scoped = filterByScope(results, received.scope);
|
|
6599
|
+
const maxActual = scoped.reduce((max, r) => r.value > max ? r.value : max, 0);
|
|
6600
|
+
const pass = maxActual <= maxDepth;
|
|
6601
|
+
return {
|
|
6602
|
+
pass,
|
|
6603
|
+
message: () => pass ? `Expected dependency depth in '${received.scope}' to exceed ${maxDepth} but it was ${maxActual}` : `Module '${received.scope}' has dependency depth ${maxActual} (limit: ${maxDepth})`
|
|
6604
|
+
};
|
|
6605
|
+
}
|
|
6606
|
+
var archMatchers = {
|
|
6607
|
+
toHaveNoCircularDeps,
|
|
6608
|
+
toHaveNoLayerViolations,
|
|
6609
|
+
toMatchBaseline,
|
|
6610
|
+
toHaveMaxComplexity,
|
|
6611
|
+
toHaveMaxCoupling,
|
|
6612
|
+
toHaveMaxFileCount,
|
|
6613
|
+
toNotDependOn,
|
|
6614
|
+
toHaveMaxDepDepth
|
|
6615
|
+
};
|
|
6616
|
+
|
|
4894
6617
|
// src/state/types.ts
|
|
4895
|
-
var
|
|
4896
|
-
var FailureEntrySchema =
|
|
4897
|
-
date:
|
|
4898
|
-
skill:
|
|
4899
|
-
type:
|
|
4900
|
-
description:
|
|
6618
|
+
var import_zod4 = require("zod");
|
|
6619
|
+
var FailureEntrySchema = import_zod4.z.object({
|
|
6620
|
+
date: import_zod4.z.string(),
|
|
6621
|
+
skill: import_zod4.z.string(),
|
|
6622
|
+
type: import_zod4.z.string(),
|
|
6623
|
+
description: import_zod4.z.string()
|
|
4901
6624
|
});
|
|
4902
|
-
var HandoffSchema =
|
|
4903
|
-
timestamp:
|
|
4904
|
-
fromSkill:
|
|
4905
|
-
phase:
|
|
4906
|
-
summary:
|
|
4907
|
-
completed:
|
|
4908
|
-
pending:
|
|
4909
|
-
concerns:
|
|
4910
|
-
decisions:
|
|
4911
|
-
|
|
4912
|
-
what:
|
|
4913
|
-
why:
|
|
6625
|
+
var HandoffSchema = import_zod4.z.object({
|
|
6626
|
+
timestamp: import_zod4.z.string(),
|
|
6627
|
+
fromSkill: import_zod4.z.string(),
|
|
6628
|
+
phase: import_zod4.z.string(),
|
|
6629
|
+
summary: import_zod4.z.string(),
|
|
6630
|
+
completed: import_zod4.z.array(import_zod4.z.string()).default([]),
|
|
6631
|
+
pending: import_zod4.z.array(import_zod4.z.string()).default([]),
|
|
6632
|
+
concerns: import_zod4.z.array(import_zod4.z.string()).default([]),
|
|
6633
|
+
decisions: import_zod4.z.array(
|
|
6634
|
+
import_zod4.z.object({
|
|
6635
|
+
what: import_zod4.z.string(),
|
|
6636
|
+
why: import_zod4.z.string()
|
|
4914
6637
|
})
|
|
4915
6638
|
).default([]),
|
|
4916
|
-
blockers:
|
|
4917
|
-
contextKeywords:
|
|
6639
|
+
blockers: import_zod4.z.array(import_zod4.z.string()).default([]),
|
|
6640
|
+
contextKeywords: import_zod4.z.array(import_zod4.z.string()).default([])
|
|
4918
6641
|
});
|
|
4919
|
-
var GateCheckSchema =
|
|
4920
|
-
name:
|
|
4921
|
-
passed:
|
|
4922
|
-
command:
|
|
4923
|
-
output:
|
|
4924
|
-
duration:
|
|
6642
|
+
var GateCheckSchema = import_zod4.z.object({
|
|
6643
|
+
name: import_zod4.z.string(),
|
|
6644
|
+
passed: import_zod4.z.boolean(),
|
|
6645
|
+
command: import_zod4.z.string(),
|
|
6646
|
+
output: import_zod4.z.string().optional(),
|
|
6647
|
+
duration: import_zod4.z.number().optional()
|
|
4925
6648
|
});
|
|
4926
|
-
var GateResultSchema =
|
|
4927
|
-
passed:
|
|
4928
|
-
checks:
|
|
6649
|
+
var GateResultSchema = import_zod4.z.object({
|
|
6650
|
+
passed: import_zod4.z.boolean(),
|
|
6651
|
+
checks: import_zod4.z.array(GateCheckSchema)
|
|
4929
6652
|
});
|
|
4930
|
-
var GateConfigSchema =
|
|
4931
|
-
checks:
|
|
4932
|
-
|
|
4933
|
-
name:
|
|
4934
|
-
command:
|
|
6653
|
+
var GateConfigSchema = import_zod4.z.object({
|
|
6654
|
+
checks: import_zod4.z.array(
|
|
6655
|
+
import_zod4.z.object({
|
|
6656
|
+
name: import_zod4.z.string(),
|
|
6657
|
+
command: import_zod4.z.string()
|
|
4935
6658
|
})
|
|
4936
6659
|
).optional(),
|
|
4937
|
-
trace:
|
|
6660
|
+
trace: import_zod4.z.boolean().optional()
|
|
4938
6661
|
});
|
|
4939
|
-
var HarnessStateSchema =
|
|
4940
|
-
schemaVersion:
|
|
4941
|
-
position:
|
|
4942
|
-
phase:
|
|
4943
|
-
task:
|
|
6662
|
+
var HarnessStateSchema = import_zod4.z.object({
|
|
6663
|
+
schemaVersion: import_zod4.z.literal(1),
|
|
6664
|
+
position: import_zod4.z.object({
|
|
6665
|
+
phase: import_zod4.z.string().optional(),
|
|
6666
|
+
task: import_zod4.z.string().optional()
|
|
4944
6667
|
}).default({}),
|
|
4945
|
-
decisions:
|
|
4946
|
-
|
|
4947
|
-
date:
|
|
4948
|
-
decision:
|
|
4949
|
-
context:
|
|
6668
|
+
decisions: import_zod4.z.array(
|
|
6669
|
+
import_zod4.z.object({
|
|
6670
|
+
date: import_zod4.z.string(),
|
|
6671
|
+
decision: import_zod4.z.string(),
|
|
6672
|
+
context: import_zod4.z.string()
|
|
4950
6673
|
})
|
|
4951
6674
|
).default([]),
|
|
4952
|
-
blockers:
|
|
4953
|
-
|
|
4954
|
-
id:
|
|
4955
|
-
description:
|
|
4956
|
-
status:
|
|
6675
|
+
blockers: import_zod4.z.array(
|
|
6676
|
+
import_zod4.z.object({
|
|
6677
|
+
id: import_zod4.z.string(),
|
|
6678
|
+
description: import_zod4.z.string(),
|
|
6679
|
+
status: import_zod4.z.enum(["open", "resolved"])
|
|
4957
6680
|
})
|
|
4958
6681
|
).default([]),
|
|
4959
|
-
progress:
|
|
4960
|
-
lastSession:
|
|
4961
|
-
date:
|
|
4962
|
-
summary:
|
|
4963
|
-
lastSkill:
|
|
4964
|
-
pendingTasks:
|
|
6682
|
+
progress: import_zod4.z.record(import_zod4.z.enum(["pending", "in_progress", "complete"])).default({}),
|
|
6683
|
+
lastSession: import_zod4.z.object({
|
|
6684
|
+
date: import_zod4.z.string(),
|
|
6685
|
+
summary: import_zod4.z.string(),
|
|
6686
|
+
lastSkill: import_zod4.z.string().optional(),
|
|
6687
|
+
pendingTasks: import_zod4.z.array(import_zod4.z.string()).optional()
|
|
4965
6688
|
}).optional()
|
|
4966
6689
|
});
|
|
4967
6690
|
var DEFAULT_STATE = {
|
|
@@ -4973,27 +6696,27 @@ var DEFAULT_STATE = {
|
|
|
4973
6696
|
};
|
|
4974
6697
|
|
|
4975
6698
|
// src/state/state-manager.ts
|
|
4976
|
-
var
|
|
6699
|
+
var fs6 = __toESM(require("fs"));
|
|
4977
6700
|
var path3 = __toESM(require("path"));
|
|
4978
6701
|
var import_child_process2 = require("child_process");
|
|
4979
6702
|
|
|
4980
6703
|
// src/state/stream-resolver.ts
|
|
4981
|
-
var
|
|
6704
|
+
var fs5 = __toESM(require("fs"));
|
|
4982
6705
|
var path2 = __toESM(require("path"));
|
|
4983
6706
|
var import_child_process = require("child_process");
|
|
4984
6707
|
|
|
4985
6708
|
// src/state/stream-types.ts
|
|
4986
|
-
var
|
|
4987
|
-
var StreamInfoSchema =
|
|
4988
|
-
name:
|
|
4989
|
-
branch:
|
|
4990
|
-
createdAt:
|
|
4991
|
-
lastActiveAt:
|
|
6709
|
+
var import_zod5 = require("zod");
|
|
6710
|
+
var StreamInfoSchema = import_zod5.z.object({
|
|
6711
|
+
name: import_zod5.z.string(),
|
|
6712
|
+
branch: import_zod5.z.string().optional(),
|
|
6713
|
+
createdAt: import_zod5.z.string(),
|
|
6714
|
+
lastActiveAt: import_zod5.z.string()
|
|
4992
6715
|
});
|
|
4993
|
-
var StreamIndexSchema =
|
|
4994
|
-
schemaVersion:
|
|
4995
|
-
activeStream:
|
|
4996
|
-
streams:
|
|
6716
|
+
var StreamIndexSchema = import_zod5.z.object({
|
|
6717
|
+
schemaVersion: import_zod5.z.literal(1),
|
|
6718
|
+
activeStream: import_zod5.z.string().nullable(),
|
|
6719
|
+
streams: import_zod5.z.record(StreamInfoSchema)
|
|
4997
6720
|
});
|
|
4998
6721
|
var DEFAULT_STREAM_INDEX = {
|
|
4999
6722
|
schemaVersion: 1,
|
|
@@ -5024,11 +6747,11 @@ function validateStreamName(name) {
|
|
|
5024
6747
|
}
|
|
5025
6748
|
async function loadStreamIndex(projectPath) {
|
|
5026
6749
|
const idxPath = indexPath(projectPath);
|
|
5027
|
-
if (!
|
|
6750
|
+
if (!fs5.existsSync(idxPath)) {
|
|
5028
6751
|
return (0, import_types.Ok)({ ...DEFAULT_STREAM_INDEX, streams: {} });
|
|
5029
6752
|
}
|
|
5030
6753
|
try {
|
|
5031
|
-
const raw =
|
|
6754
|
+
const raw = fs5.readFileSync(idxPath, "utf-8");
|
|
5032
6755
|
const parsed = JSON.parse(raw);
|
|
5033
6756
|
const result = StreamIndexSchema.safeParse(parsed);
|
|
5034
6757
|
if (!result.success) {
|
|
@@ -5046,8 +6769,8 @@ async function loadStreamIndex(projectPath) {
|
|
|
5046
6769
|
async function saveStreamIndex(projectPath, index) {
|
|
5047
6770
|
const dir = streamsDir(projectPath);
|
|
5048
6771
|
try {
|
|
5049
|
-
|
|
5050
|
-
|
|
6772
|
+
fs5.mkdirSync(dir, { recursive: true });
|
|
6773
|
+
fs5.writeFileSync(indexPath(projectPath), JSON.stringify(index, null, 2));
|
|
5051
6774
|
return (0, import_types.Ok)(void 0);
|
|
5052
6775
|
} catch (error) {
|
|
5053
6776
|
return (0, import_types.Err)(
|
|
@@ -5129,7 +6852,7 @@ async function createStream(projectPath, name, branch) {
|
|
|
5129
6852
|
}
|
|
5130
6853
|
const streamPath = path2.join(streamsDir(projectPath), name);
|
|
5131
6854
|
try {
|
|
5132
|
-
|
|
6855
|
+
fs5.mkdirSync(streamPath, { recursive: true });
|
|
5133
6856
|
} catch (error) {
|
|
5134
6857
|
return (0, import_types.Err)(
|
|
5135
6858
|
new Error(
|
|
@@ -5173,9 +6896,9 @@ async function archiveStream(projectPath, name) {
|
|
|
5173
6896
|
const streamPath = path2.join(streamsDir(projectPath), name);
|
|
5174
6897
|
const archiveDir = path2.join(projectPath, HARNESS_DIR, "archive", "streams");
|
|
5175
6898
|
try {
|
|
5176
|
-
|
|
6899
|
+
fs5.mkdirSync(archiveDir, { recursive: true });
|
|
5177
6900
|
const date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
5178
|
-
|
|
6901
|
+
fs5.renameSync(streamPath, path2.join(archiveDir, `${name}-${date}`));
|
|
5179
6902
|
} catch (error) {
|
|
5180
6903
|
return (0, import_types.Err)(
|
|
5181
6904
|
new Error(
|
|
@@ -5198,18 +6921,18 @@ function getStreamForBranch(index, branch) {
|
|
|
5198
6921
|
var STATE_FILES = ["state.json", "handoff.json", "learnings.md", "failures.md"];
|
|
5199
6922
|
async function migrateToStreams(projectPath) {
|
|
5200
6923
|
const harnessDir = path2.join(projectPath, HARNESS_DIR);
|
|
5201
|
-
if (
|
|
6924
|
+
if (fs5.existsSync(indexPath(projectPath))) {
|
|
5202
6925
|
return (0, import_types.Ok)(void 0);
|
|
5203
6926
|
}
|
|
5204
|
-
const filesToMove = STATE_FILES.filter((f) =>
|
|
6927
|
+
const filesToMove = STATE_FILES.filter((f) => fs5.existsSync(path2.join(harnessDir, f)));
|
|
5205
6928
|
if (filesToMove.length === 0) {
|
|
5206
6929
|
return (0, import_types.Ok)(void 0);
|
|
5207
6930
|
}
|
|
5208
6931
|
const defaultDir = path2.join(streamsDir(projectPath), "default");
|
|
5209
6932
|
try {
|
|
5210
|
-
|
|
6933
|
+
fs5.mkdirSync(defaultDir, { recursive: true });
|
|
5211
6934
|
for (const file of filesToMove) {
|
|
5212
|
-
|
|
6935
|
+
fs5.renameSync(path2.join(harnessDir, file), path2.join(defaultDir, file));
|
|
5213
6936
|
}
|
|
5214
6937
|
} catch (error) {
|
|
5215
6938
|
return (0, import_types.Err)(
|
|
@@ -5250,7 +6973,7 @@ function evictIfNeeded(map) {
|
|
|
5250
6973
|
}
|
|
5251
6974
|
async function getStateDir(projectPath, stream) {
|
|
5252
6975
|
const streamsIndexPath = path3.join(projectPath, HARNESS_DIR2, "streams", INDEX_FILE2);
|
|
5253
|
-
const hasStreams =
|
|
6976
|
+
const hasStreams = fs6.existsSync(streamsIndexPath);
|
|
5254
6977
|
if (stream || hasStreams) {
|
|
5255
6978
|
const result = await resolveStreamPath(projectPath, stream ? { stream } : void 0);
|
|
5256
6979
|
if (result.ok) {
|
|
@@ -5268,10 +6991,10 @@ async function loadState(projectPath, stream) {
|
|
|
5268
6991
|
if (!dirResult.ok) return dirResult;
|
|
5269
6992
|
const stateDir = dirResult.value;
|
|
5270
6993
|
const statePath = path3.join(stateDir, STATE_FILE);
|
|
5271
|
-
if (!
|
|
6994
|
+
if (!fs6.existsSync(statePath)) {
|
|
5272
6995
|
return (0, import_types.Ok)({ ...DEFAULT_STATE });
|
|
5273
6996
|
}
|
|
5274
|
-
const raw =
|
|
6997
|
+
const raw = fs6.readFileSync(statePath, "utf-8");
|
|
5275
6998
|
const parsed = JSON.parse(raw);
|
|
5276
6999
|
const result = HarnessStateSchema.safeParse(parsed);
|
|
5277
7000
|
if (!result.success) {
|
|
@@ -5290,8 +7013,8 @@ async function saveState(projectPath, state, stream) {
|
|
|
5290
7013
|
if (!dirResult.ok) return dirResult;
|
|
5291
7014
|
const stateDir = dirResult.value;
|
|
5292
7015
|
const statePath = path3.join(stateDir, STATE_FILE);
|
|
5293
|
-
|
|
5294
|
-
|
|
7016
|
+
fs6.mkdirSync(stateDir, { recursive: true });
|
|
7017
|
+
fs6.writeFileSync(statePath, JSON.stringify(state, null, 2));
|
|
5295
7018
|
return (0, import_types.Ok)(void 0);
|
|
5296
7019
|
} catch (error) {
|
|
5297
7020
|
return (0, import_types.Err)(
|
|
@@ -5305,7 +7028,7 @@ async function appendLearning(projectPath, learning, skillName, outcome, stream)
|
|
|
5305
7028
|
if (!dirResult.ok) return dirResult;
|
|
5306
7029
|
const stateDir = dirResult.value;
|
|
5307
7030
|
const learningsPath = path3.join(stateDir, LEARNINGS_FILE);
|
|
5308
|
-
|
|
7031
|
+
fs6.mkdirSync(stateDir, { recursive: true });
|
|
5309
7032
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
5310
7033
|
let entry;
|
|
5311
7034
|
if (skillName && outcome) {
|
|
@@ -5321,11 +7044,11 @@ async function appendLearning(projectPath, learning, skillName, outcome, stream)
|
|
|
5321
7044
|
- **${timestamp}:** ${learning}
|
|
5322
7045
|
`;
|
|
5323
7046
|
}
|
|
5324
|
-
if (!
|
|
5325
|
-
|
|
7047
|
+
if (!fs6.existsSync(learningsPath)) {
|
|
7048
|
+
fs6.writeFileSync(learningsPath, `# Learnings
|
|
5326
7049
|
${entry}`);
|
|
5327
7050
|
} else {
|
|
5328
|
-
|
|
7051
|
+
fs6.appendFileSync(learningsPath, entry);
|
|
5329
7052
|
}
|
|
5330
7053
|
learningsCacheMap.delete(learningsPath);
|
|
5331
7054
|
return (0, import_types.Ok)(void 0);
|
|
@@ -5343,17 +7066,17 @@ async function loadRelevantLearnings(projectPath, skillName, stream) {
|
|
|
5343
7066
|
if (!dirResult.ok) return dirResult;
|
|
5344
7067
|
const stateDir = dirResult.value;
|
|
5345
7068
|
const learningsPath = path3.join(stateDir, LEARNINGS_FILE);
|
|
5346
|
-
if (!
|
|
7069
|
+
if (!fs6.existsSync(learningsPath)) {
|
|
5347
7070
|
return (0, import_types.Ok)([]);
|
|
5348
7071
|
}
|
|
5349
|
-
const stats =
|
|
7072
|
+
const stats = fs6.statSync(learningsPath);
|
|
5350
7073
|
const cacheKey = learningsPath;
|
|
5351
7074
|
const cached = learningsCacheMap.get(cacheKey);
|
|
5352
7075
|
let entries;
|
|
5353
7076
|
if (cached && cached.mtimeMs === stats.mtimeMs) {
|
|
5354
7077
|
entries = cached.entries;
|
|
5355
7078
|
} else {
|
|
5356
|
-
const content =
|
|
7079
|
+
const content = fs6.readFileSync(learningsPath, "utf-8");
|
|
5357
7080
|
const lines = content.split("\n");
|
|
5358
7081
|
entries = [];
|
|
5359
7082
|
let currentBlock = [];
|
|
@@ -5396,16 +7119,16 @@ async function appendFailure(projectPath, description, skillName, type, stream)
|
|
|
5396
7119
|
if (!dirResult.ok) return dirResult;
|
|
5397
7120
|
const stateDir = dirResult.value;
|
|
5398
7121
|
const failuresPath = path3.join(stateDir, FAILURES_FILE);
|
|
5399
|
-
|
|
7122
|
+
fs6.mkdirSync(stateDir, { recursive: true });
|
|
5400
7123
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
5401
7124
|
const entry = `
|
|
5402
7125
|
- **${timestamp} [skill:${skillName}] [type:${type}]:** ${description}
|
|
5403
7126
|
`;
|
|
5404
|
-
if (!
|
|
5405
|
-
|
|
7127
|
+
if (!fs6.existsSync(failuresPath)) {
|
|
7128
|
+
fs6.writeFileSync(failuresPath, `# Failures
|
|
5406
7129
|
${entry}`);
|
|
5407
7130
|
} else {
|
|
5408
|
-
|
|
7131
|
+
fs6.appendFileSync(failuresPath, entry);
|
|
5409
7132
|
}
|
|
5410
7133
|
failuresCacheMap.delete(failuresPath);
|
|
5411
7134
|
return (0, import_types.Ok)(void 0);
|
|
@@ -5423,16 +7146,16 @@ async function loadFailures(projectPath, stream) {
|
|
|
5423
7146
|
if (!dirResult.ok) return dirResult;
|
|
5424
7147
|
const stateDir = dirResult.value;
|
|
5425
7148
|
const failuresPath = path3.join(stateDir, FAILURES_FILE);
|
|
5426
|
-
if (!
|
|
7149
|
+
if (!fs6.existsSync(failuresPath)) {
|
|
5427
7150
|
return (0, import_types.Ok)([]);
|
|
5428
7151
|
}
|
|
5429
|
-
const stats =
|
|
7152
|
+
const stats = fs6.statSync(failuresPath);
|
|
5430
7153
|
const cacheKey = failuresPath;
|
|
5431
7154
|
const cached = failuresCacheMap.get(cacheKey);
|
|
5432
7155
|
if (cached && cached.mtimeMs === stats.mtimeMs) {
|
|
5433
7156
|
return (0, import_types.Ok)(cached.entries);
|
|
5434
7157
|
}
|
|
5435
|
-
const content =
|
|
7158
|
+
const content = fs6.readFileSync(failuresPath, "utf-8");
|
|
5436
7159
|
const entries = [];
|
|
5437
7160
|
for (const line of content.split("\n")) {
|
|
5438
7161
|
const match = line.match(FAILURE_LINE_REGEX);
|
|
@@ -5462,19 +7185,19 @@ async function archiveFailures(projectPath, stream) {
|
|
|
5462
7185
|
if (!dirResult.ok) return dirResult;
|
|
5463
7186
|
const stateDir = dirResult.value;
|
|
5464
7187
|
const failuresPath = path3.join(stateDir, FAILURES_FILE);
|
|
5465
|
-
if (!
|
|
7188
|
+
if (!fs6.existsSync(failuresPath)) {
|
|
5466
7189
|
return (0, import_types.Ok)(void 0);
|
|
5467
7190
|
}
|
|
5468
7191
|
const archiveDir = path3.join(stateDir, "archive");
|
|
5469
|
-
|
|
7192
|
+
fs6.mkdirSync(archiveDir, { recursive: true });
|
|
5470
7193
|
const date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
5471
7194
|
let archiveName = `failures-${date}.md`;
|
|
5472
7195
|
let counter = 2;
|
|
5473
|
-
while (
|
|
7196
|
+
while (fs6.existsSync(path3.join(archiveDir, archiveName))) {
|
|
5474
7197
|
archiveName = `failures-${date}-${counter}.md`;
|
|
5475
7198
|
counter++;
|
|
5476
7199
|
}
|
|
5477
|
-
|
|
7200
|
+
fs6.renameSync(failuresPath, path3.join(archiveDir, archiveName));
|
|
5478
7201
|
failuresCacheMap.delete(failuresPath);
|
|
5479
7202
|
return (0, import_types.Ok)(void 0);
|
|
5480
7203
|
} catch (error) {
|
|
@@ -5491,8 +7214,8 @@ async function saveHandoff(projectPath, handoff, stream) {
|
|
|
5491
7214
|
if (!dirResult.ok) return dirResult;
|
|
5492
7215
|
const stateDir = dirResult.value;
|
|
5493
7216
|
const handoffPath = path3.join(stateDir, HANDOFF_FILE);
|
|
5494
|
-
|
|
5495
|
-
|
|
7217
|
+
fs6.mkdirSync(stateDir, { recursive: true });
|
|
7218
|
+
fs6.writeFileSync(handoffPath, JSON.stringify(handoff, null, 2));
|
|
5496
7219
|
return (0, import_types.Ok)(void 0);
|
|
5497
7220
|
} catch (error) {
|
|
5498
7221
|
return (0, import_types.Err)(
|
|
@@ -5506,10 +7229,10 @@ async function loadHandoff(projectPath, stream) {
|
|
|
5506
7229
|
if (!dirResult.ok) return dirResult;
|
|
5507
7230
|
const stateDir = dirResult.value;
|
|
5508
7231
|
const handoffPath = path3.join(stateDir, HANDOFF_FILE);
|
|
5509
|
-
if (!
|
|
7232
|
+
if (!fs6.existsSync(handoffPath)) {
|
|
5510
7233
|
return (0, import_types.Ok)(null);
|
|
5511
7234
|
}
|
|
5512
|
-
const raw =
|
|
7235
|
+
const raw = fs6.readFileSync(handoffPath, "utf-8");
|
|
5513
7236
|
const parsed = JSON.parse(raw);
|
|
5514
7237
|
const result = HandoffSchema.safeParse(parsed);
|
|
5515
7238
|
if (!result.success) {
|
|
@@ -5527,8 +7250,8 @@ async function runMechanicalGate(projectPath) {
|
|
|
5527
7250
|
const gateConfigPath = path3.join(harnessDir, GATE_CONFIG_FILE);
|
|
5528
7251
|
try {
|
|
5529
7252
|
let checks = [];
|
|
5530
|
-
if (
|
|
5531
|
-
const raw = JSON.parse(
|
|
7253
|
+
if (fs6.existsSync(gateConfigPath)) {
|
|
7254
|
+
const raw = JSON.parse(fs6.readFileSync(gateConfigPath, "utf-8"));
|
|
5532
7255
|
const config = GateConfigSchema.safeParse(raw);
|
|
5533
7256
|
if (config.success && config.data.checks) {
|
|
5534
7257
|
checks = config.data.checks;
|
|
@@ -5536,19 +7259,19 @@ async function runMechanicalGate(projectPath) {
|
|
|
5536
7259
|
}
|
|
5537
7260
|
if (checks.length === 0) {
|
|
5538
7261
|
const packageJsonPath = path3.join(projectPath, "package.json");
|
|
5539
|
-
if (
|
|
5540
|
-
const pkg = JSON.parse(
|
|
7262
|
+
if (fs6.existsSync(packageJsonPath)) {
|
|
7263
|
+
const pkg = JSON.parse(fs6.readFileSync(packageJsonPath, "utf-8"));
|
|
5541
7264
|
const scripts = pkg.scripts || {};
|
|
5542
7265
|
if (scripts.test) checks.push({ name: "test", command: "npm test" });
|
|
5543
7266
|
if (scripts.lint) checks.push({ name: "lint", command: "npm run lint" });
|
|
5544
7267
|
if (scripts.typecheck) checks.push({ name: "typecheck", command: "npm run typecheck" });
|
|
5545
7268
|
if (scripts.build) checks.push({ name: "build", command: "npm run build" });
|
|
5546
7269
|
}
|
|
5547
|
-
if (
|
|
7270
|
+
if (fs6.existsSync(path3.join(projectPath, "go.mod"))) {
|
|
5548
7271
|
checks.push({ name: "test", command: "go test ./..." });
|
|
5549
7272
|
checks.push({ name: "build", command: "go build ./..." });
|
|
5550
7273
|
}
|
|
5551
|
-
if (
|
|
7274
|
+
if (fs6.existsSync(path3.join(projectPath, "pyproject.toml")) || fs6.existsSync(path3.join(projectPath, "setup.py"))) {
|
|
5552
7275
|
checks.push({ name: "test", command: "python -m pytest" });
|
|
5553
7276
|
}
|
|
5554
7277
|
}
|
|
@@ -5751,7 +7474,7 @@ async function runMultiTurnPipeline(initialContext, turnExecutor, options) {
|
|
|
5751
7474
|
}
|
|
5752
7475
|
|
|
5753
7476
|
// src/security/scanner.ts
|
|
5754
|
-
var
|
|
7477
|
+
var fs8 = __toESM(require("fs/promises"));
|
|
5755
7478
|
|
|
5756
7479
|
// src/security/rules/registry.ts
|
|
5757
7480
|
var RuleRegistry = class {
|
|
@@ -5782,7 +7505,7 @@ var RuleRegistry = class {
|
|
|
5782
7505
|
};
|
|
5783
7506
|
|
|
5784
7507
|
// src/security/config.ts
|
|
5785
|
-
var
|
|
7508
|
+
var import_zod6 = require("zod");
|
|
5786
7509
|
|
|
5787
7510
|
// src/security/types.ts
|
|
5788
7511
|
var DEFAULT_SECURITY_CONFIG = {
|
|
@@ -5793,19 +7516,19 @@ var DEFAULT_SECURITY_CONFIG = {
|
|
|
5793
7516
|
};
|
|
5794
7517
|
|
|
5795
7518
|
// src/security/config.ts
|
|
5796
|
-
var RuleOverrideSchema =
|
|
5797
|
-
var SecurityConfigSchema =
|
|
5798
|
-
enabled:
|
|
5799
|
-
strict:
|
|
5800
|
-
rules:
|
|
5801
|
-
exclude:
|
|
5802
|
-
external:
|
|
5803
|
-
semgrep:
|
|
5804
|
-
enabled:
|
|
5805
|
-
rulesets:
|
|
7519
|
+
var RuleOverrideSchema = import_zod6.z.enum(["off", "error", "warning", "info"]);
|
|
7520
|
+
var SecurityConfigSchema = import_zod6.z.object({
|
|
7521
|
+
enabled: import_zod6.z.boolean().default(true),
|
|
7522
|
+
strict: import_zod6.z.boolean().default(false),
|
|
7523
|
+
rules: import_zod6.z.record(import_zod6.z.string(), RuleOverrideSchema).optional().default({}),
|
|
7524
|
+
exclude: import_zod6.z.array(import_zod6.z.string()).optional().default(["**/node_modules/**", "**/dist/**", "**/*.test.ts", "**/fixtures/**"]),
|
|
7525
|
+
external: import_zod6.z.object({
|
|
7526
|
+
semgrep: import_zod6.z.object({
|
|
7527
|
+
enabled: import_zod6.z.union([import_zod6.z.literal("auto"), import_zod6.z.boolean()]).default("auto"),
|
|
7528
|
+
rulesets: import_zod6.z.array(import_zod6.z.string()).optional()
|
|
5806
7529
|
}).optional(),
|
|
5807
|
-
gitleaks:
|
|
5808
|
-
enabled:
|
|
7530
|
+
gitleaks: import_zod6.z.object({
|
|
7531
|
+
enabled: import_zod6.z.union([import_zod6.z.literal("auto"), import_zod6.z.boolean()]).default("auto")
|
|
5809
7532
|
}).optional()
|
|
5810
7533
|
}).optional()
|
|
5811
7534
|
});
|
|
@@ -5838,15 +7561,15 @@ function resolveRuleSeverity(ruleId, defaultSeverity, overrides, strict) {
|
|
|
5838
7561
|
}
|
|
5839
7562
|
|
|
5840
7563
|
// src/security/stack-detector.ts
|
|
5841
|
-
var
|
|
7564
|
+
var fs7 = __toESM(require("fs"));
|
|
5842
7565
|
var path4 = __toESM(require("path"));
|
|
5843
7566
|
function detectStack(projectRoot) {
|
|
5844
7567
|
const stacks = [];
|
|
5845
7568
|
const pkgJsonPath = path4.join(projectRoot, "package.json");
|
|
5846
|
-
if (
|
|
7569
|
+
if (fs7.existsSync(pkgJsonPath)) {
|
|
5847
7570
|
stacks.push("node");
|
|
5848
7571
|
try {
|
|
5849
|
-
const pkgJson = JSON.parse(
|
|
7572
|
+
const pkgJson = JSON.parse(fs7.readFileSync(pkgJsonPath, "utf-8"));
|
|
5850
7573
|
const allDeps = {
|
|
5851
7574
|
...pkgJson.dependencies,
|
|
5852
7575
|
...pkgJson.devDependencies
|
|
@@ -5862,12 +7585,12 @@ function detectStack(projectRoot) {
|
|
|
5862
7585
|
}
|
|
5863
7586
|
}
|
|
5864
7587
|
const goModPath = path4.join(projectRoot, "go.mod");
|
|
5865
|
-
if (
|
|
7588
|
+
if (fs7.existsSync(goModPath)) {
|
|
5866
7589
|
stacks.push("go");
|
|
5867
7590
|
}
|
|
5868
7591
|
const requirementsPath = path4.join(projectRoot, "requirements.txt");
|
|
5869
7592
|
const pyprojectPath = path4.join(projectRoot, "pyproject.toml");
|
|
5870
|
-
if (
|
|
7593
|
+
if (fs7.existsSync(requirementsPath) || fs7.existsSync(pyprojectPath)) {
|
|
5871
7594
|
stacks.push("python");
|
|
5872
7595
|
}
|
|
5873
7596
|
return stacks;
|
|
@@ -6294,7 +8017,7 @@ var SecurityScanner = class {
|
|
|
6294
8017
|
}
|
|
6295
8018
|
async scanFile(filePath) {
|
|
6296
8019
|
if (!this.config.enabled) return [];
|
|
6297
|
-
const content = await
|
|
8020
|
+
const content = await fs8.readFile(filePath, "utf-8");
|
|
6298
8021
|
return this.scanContent(content, filePath, 1);
|
|
6299
8022
|
}
|
|
6300
8023
|
async scanFiles(filePaths) {
|
|
@@ -6327,7 +8050,8 @@ var ALL_CHECKS = [
|
|
|
6327
8050
|
"entropy",
|
|
6328
8051
|
"security",
|
|
6329
8052
|
"perf",
|
|
6330
|
-
"phase-gate"
|
|
8053
|
+
"phase-gate",
|
|
8054
|
+
"arch"
|
|
6331
8055
|
];
|
|
6332
8056
|
async function runSingleCheck(name, projectRoot, config) {
|
|
6333
8057
|
const start = Date.now();
|
|
@@ -6391,7 +8115,17 @@ async function runSingleCheck(name, projectRoot, config) {
|
|
|
6391
8115
|
}
|
|
6392
8116
|
case "docs": {
|
|
6393
8117
|
const docsDir = path5.join(projectRoot, config.docsDir ?? "docs");
|
|
6394
|
-
const
|
|
8118
|
+
const entropyConfig = config.entropy || {};
|
|
8119
|
+
const result = await checkDocCoverage("project", {
|
|
8120
|
+
docsDir,
|
|
8121
|
+
sourceDir: projectRoot,
|
|
8122
|
+
excludePatterns: entropyConfig.excludePatterns || [
|
|
8123
|
+
"**/node_modules/**",
|
|
8124
|
+
"**/dist/**",
|
|
8125
|
+
"**/*.test.ts",
|
|
8126
|
+
"**/fixtures/**"
|
|
8127
|
+
]
|
|
8128
|
+
});
|
|
6395
8129
|
if (!result.ok) {
|
|
6396
8130
|
issues.push({ severity: "warning", message: result.error.message });
|
|
6397
8131
|
} else if (result.value.gaps.length > 0) {
|
|
@@ -6466,11 +8200,13 @@ async function runSingleCheck(name, projectRoot, config) {
|
|
|
6466
8200
|
break;
|
|
6467
8201
|
}
|
|
6468
8202
|
case "perf": {
|
|
8203
|
+
const perfConfig = config.performance || {};
|
|
6469
8204
|
const perfAnalyzer = new EntropyAnalyzer({
|
|
6470
8205
|
rootDir: projectRoot,
|
|
6471
8206
|
analyze: {
|
|
6472
|
-
complexity: true,
|
|
6473
|
-
coupling: true
|
|
8207
|
+
complexity: perfConfig.complexity || true,
|
|
8208
|
+
coupling: perfConfig.coupling || true,
|
|
8209
|
+
sizeBudget: perfConfig.sizeBudget || false
|
|
6474
8210
|
}
|
|
6475
8211
|
});
|
|
6476
8212
|
const perfResult = await perfAnalyzer.analyze();
|
|
@@ -6511,6 +8247,43 @@ async function runSingleCheck(name, projectRoot, config) {
|
|
|
6511
8247
|
});
|
|
6512
8248
|
break;
|
|
6513
8249
|
}
|
|
8250
|
+
case "arch": {
|
|
8251
|
+
const rawArchConfig = config.architecture;
|
|
8252
|
+
const archConfig = ArchConfigSchema.parse(rawArchConfig ?? {});
|
|
8253
|
+
if (!archConfig.enabled) break;
|
|
8254
|
+
const results = await runAll(archConfig, projectRoot);
|
|
8255
|
+
const baselineManager = new ArchBaselineManager(projectRoot, archConfig.baselinePath);
|
|
8256
|
+
const baseline = baselineManager.load();
|
|
8257
|
+
if (baseline) {
|
|
8258
|
+
const diffResult = diff(results, baseline);
|
|
8259
|
+
if (!diffResult.passed) {
|
|
8260
|
+
for (const v of diffResult.newViolations) {
|
|
8261
|
+
issues.push({
|
|
8262
|
+
severity: v.severity,
|
|
8263
|
+
message: `[${v.category || "arch"}] NEW: ${v.detail}`,
|
|
8264
|
+
file: v.file
|
|
8265
|
+
});
|
|
8266
|
+
}
|
|
8267
|
+
for (const r of diffResult.regressions) {
|
|
8268
|
+
issues.push({
|
|
8269
|
+
severity: "error",
|
|
8270
|
+
message: `[${r.category}] REGRESSION: ${r.currentValue} > ${r.baselineValue} (delta: ${r.delta})`
|
|
8271
|
+
});
|
|
8272
|
+
}
|
|
8273
|
+
}
|
|
8274
|
+
} else {
|
|
8275
|
+
for (const result of results) {
|
|
8276
|
+
for (const v of result.violations) {
|
|
8277
|
+
issues.push({
|
|
8278
|
+
severity: v.severity,
|
|
8279
|
+
message: `[${result.category}] ${v.detail}`,
|
|
8280
|
+
file: v.file
|
|
8281
|
+
});
|
|
8282
|
+
}
|
|
8283
|
+
}
|
|
8284
|
+
}
|
|
8285
|
+
break;
|
|
8286
|
+
}
|
|
6514
8287
|
}
|
|
6515
8288
|
} catch (error) {
|
|
6516
8289
|
issues.push({
|
|
@@ -6845,22 +8618,22 @@ var PREFIX_PATTERNS = [
|
|
|
6845
8618
|
];
|
|
6846
8619
|
var TEST_FILE_PATTERN = /\.(test|spec)\.(ts|tsx|js|jsx|mts|cts)$/;
|
|
6847
8620
|
var MD_FILE_PATTERN = /\.md$/;
|
|
6848
|
-
function detectChangeType(commitMessage,
|
|
8621
|
+
function detectChangeType(commitMessage, diff2) {
|
|
6849
8622
|
const trimmed = commitMessage.trim();
|
|
6850
8623
|
for (const { pattern, type } of PREFIX_PATTERNS) {
|
|
6851
8624
|
if (pattern.test(trimmed)) {
|
|
6852
8625
|
return type;
|
|
6853
8626
|
}
|
|
6854
8627
|
}
|
|
6855
|
-
if (
|
|
8628
|
+
if (diff2.changedFiles.length > 0 && diff2.changedFiles.every((f) => MD_FILE_PATTERN.test(f))) {
|
|
6856
8629
|
return "docs";
|
|
6857
8630
|
}
|
|
6858
|
-
const newNonTestFiles =
|
|
8631
|
+
const newNonTestFiles = diff2.newFiles.filter((f) => !TEST_FILE_PATTERN.test(f));
|
|
6859
8632
|
if (newNonTestFiles.length > 0) {
|
|
6860
8633
|
return "feature";
|
|
6861
8634
|
}
|
|
6862
|
-
const hasNewTestFile =
|
|
6863
|
-
if (
|
|
8635
|
+
const hasNewTestFile = diff2.newFiles.some((f) => TEST_FILE_PATTERN.test(f));
|
|
8636
|
+
if (diff2.totalDiffLines < 20 && hasNewTestFile) {
|
|
6864
8637
|
return "bugfix";
|
|
6865
8638
|
}
|
|
6866
8639
|
return "feature";
|
|
@@ -6889,7 +8662,7 @@ async function readContextFile(projectRoot, filePath, reason) {
|
|
|
6889
8662
|
const relPath = path7.isAbsolute(filePath) ? path7.relative(projectRoot, filePath) : filePath;
|
|
6890
8663
|
return { path: relPath, content, reason, lines };
|
|
6891
8664
|
}
|
|
6892
|
-
function
|
|
8665
|
+
function extractImportSources2(content) {
|
|
6893
8666
|
const sources = [];
|
|
6894
8667
|
const importRegex = /(?:import\s+(?:.*?\s+from\s+)?['"]([^'"]+)['"]|require\(\s*['"]([^'"]+)['"]\s*\))/g;
|
|
6895
8668
|
let match;
|
|
@@ -6931,7 +8704,7 @@ async function gatherImportContext(projectRoot, changedFiles, budget) {
|
|
|
6931
8704
|
const seen = new Set(changedFiles.map((f) => f.path));
|
|
6932
8705
|
for (const cf of changedFiles) {
|
|
6933
8706
|
if (linesGathered >= budget) break;
|
|
6934
|
-
const sources =
|
|
8707
|
+
const sources = extractImportSources2(cf.content);
|
|
6935
8708
|
for (const source of sources) {
|
|
6936
8709
|
if (linesGathered >= budget) break;
|
|
6937
8710
|
const resolved = await resolveImportPath2(projectRoot, cf.path, source);
|
|
@@ -7098,11 +8871,11 @@ async function scopeArchitectureContext(projectRoot, changedFiles, budget, optio
|
|
|
7098
8871
|
return contextFiles;
|
|
7099
8872
|
}
|
|
7100
8873
|
async function scopeContext(options) {
|
|
7101
|
-
const { projectRoot, diff, commitMessage } = options;
|
|
7102
|
-
const changeType = detectChangeType(commitMessage,
|
|
7103
|
-
const budget = computeContextBudget(
|
|
8874
|
+
const { projectRoot, diff: diff2, commitMessage } = options;
|
|
8875
|
+
const changeType = detectChangeType(commitMessage, diff2);
|
|
8876
|
+
const budget = computeContextBudget(diff2.totalDiffLines);
|
|
7104
8877
|
const changedFiles = [];
|
|
7105
|
-
for (const filePath of
|
|
8878
|
+
for (const filePath of diff2.changedFiles) {
|
|
7106
8879
|
const cf = await readContextFile(projectRoot, filePath, "changed");
|
|
7107
8880
|
if (cf) changedFiles.push(cf);
|
|
7108
8881
|
}
|
|
@@ -7122,7 +8895,7 @@ async function scopeContext(options) {
|
|
|
7122
8895
|
changedFiles: [...changedFiles],
|
|
7123
8896
|
contextFiles,
|
|
7124
8897
|
commitHistory: options.commitHistory ?? [],
|
|
7125
|
-
diffLines:
|
|
8898
|
+
diffLines: diff2.totalDiffLines,
|
|
7126
8899
|
contextLines
|
|
7127
8900
|
});
|
|
7128
8901
|
}
|
|
@@ -8132,7 +9905,7 @@ function formatGitHubSummary(options) {
|
|
|
8132
9905
|
async function runReviewPipeline(options) {
|
|
8133
9906
|
const {
|
|
8134
9907
|
projectRoot,
|
|
8135
|
-
diff,
|
|
9908
|
+
diff: diff2,
|
|
8136
9909
|
commitMessage,
|
|
8137
9910
|
flags,
|
|
8138
9911
|
graph,
|
|
@@ -8166,7 +9939,7 @@ async function runReviewPipeline(options) {
|
|
|
8166
9939
|
const mechResult = await runMechanicalChecks({
|
|
8167
9940
|
projectRoot,
|
|
8168
9941
|
config,
|
|
8169
|
-
changedFiles:
|
|
9942
|
+
changedFiles: diff2.changedFiles
|
|
8170
9943
|
});
|
|
8171
9944
|
if (mechResult.ok) {
|
|
8172
9945
|
mechanicalResult = mechResult.value;
|
|
@@ -8205,7 +9978,7 @@ async function runReviewPipeline(options) {
|
|
|
8205
9978
|
try {
|
|
8206
9979
|
contextBundles = await scopeContext({
|
|
8207
9980
|
projectRoot,
|
|
8208
|
-
diff,
|
|
9981
|
+
diff: diff2,
|
|
8209
9982
|
commitMessage,
|
|
8210
9983
|
...graph != null ? { graph } : {},
|
|
8211
9984
|
...conventionFiles != null ? { conventionFiles } : {},
|
|
@@ -8219,14 +9992,14 @@ async function runReviewPipeline(options) {
|
|
|
8219
9992
|
changedFiles: [],
|
|
8220
9993
|
contextFiles: [],
|
|
8221
9994
|
commitHistory: [],
|
|
8222
|
-
diffLines:
|
|
9995
|
+
diffLines: diff2.totalDiffLines,
|
|
8223
9996
|
contextLines: 0
|
|
8224
9997
|
}));
|
|
8225
9998
|
}
|
|
8226
9999
|
const agentResults = await fanOutReview({ bundles: contextBundles });
|
|
8227
10000
|
const rawFindings = agentResults.flatMap((r) => r.findings);
|
|
8228
10001
|
const fileContents = /* @__PURE__ */ new Map();
|
|
8229
|
-
for (const [file, content] of
|
|
10002
|
+
for (const [file, content] of diff2.fileDiffs) {
|
|
8230
10003
|
fileContents.set(file, content);
|
|
8231
10004
|
}
|
|
8232
10005
|
const validatedFindings = await validateFindings({
|
|
@@ -8262,7 +10035,7 @@ async function runReviewPipeline(options) {
|
|
|
8262
10035
|
}
|
|
8263
10036
|
|
|
8264
10037
|
// src/roadmap/parse.ts
|
|
8265
|
-
var
|
|
10038
|
+
var import_types16 = require("@harness-engineering/types");
|
|
8266
10039
|
var VALID_STATUSES = /* @__PURE__ */ new Set([
|
|
8267
10040
|
"backlog",
|
|
8268
10041
|
"planned",
|
|
@@ -8274,14 +10047,14 @@ var EM_DASH = "\u2014";
|
|
|
8274
10047
|
function parseRoadmap(markdown) {
|
|
8275
10048
|
const fmMatch = markdown.match(/^---\n([\s\S]*?)\n---/);
|
|
8276
10049
|
if (!fmMatch) {
|
|
8277
|
-
return (0,
|
|
10050
|
+
return (0, import_types16.Err)(new Error("Missing or malformed YAML frontmatter"));
|
|
8278
10051
|
}
|
|
8279
10052
|
const fmResult = parseFrontmatter(fmMatch[1]);
|
|
8280
10053
|
if (!fmResult.ok) return fmResult;
|
|
8281
10054
|
const body = markdown.slice(fmMatch[0].length);
|
|
8282
10055
|
const milestonesResult = parseMilestones(body);
|
|
8283
10056
|
if (!milestonesResult.ok) return milestonesResult;
|
|
8284
|
-
return (0,
|
|
10057
|
+
return (0, import_types16.Ok)({
|
|
8285
10058
|
frontmatter: fmResult.value,
|
|
8286
10059
|
milestones: milestonesResult.value
|
|
8287
10060
|
});
|
|
@@ -8301,7 +10074,7 @@ function parseFrontmatter(raw) {
|
|
|
8301
10074
|
const lastSynced = map.get("last_synced");
|
|
8302
10075
|
const lastManualEdit = map.get("last_manual_edit");
|
|
8303
10076
|
if (!project || !versionStr || !lastSynced || !lastManualEdit) {
|
|
8304
|
-
return (0,
|
|
10077
|
+
return (0, import_types16.Err)(
|
|
8305
10078
|
new Error(
|
|
8306
10079
|
"Frontmatter missing required fields: project, version, last_synced, last_manual_edit"
|
|
8307
10080
|
)
|
|
@@ -8309,9 +10082,9 @@ function parseFrontmatter(raw) {
|
|
|
8309
10082
|
}
|
|
8310
10083
|
const version = parseInt(versionStr, 10);
|
|
8311
10084
|
if (isNaN(version)) {
|
|
8312
|
-
return (0,
|
|
10085
|
+
return (0, import_types16.Err)(new Error("Frontmatter version must be a number"));
|
|
8313
10086
|
}
|
|
8314
|
-
return (0,
|
|
10087
|
+
return (0, import_types16.Ok)({ project, version, lastSynced, lastManualEdit });
|
|
8315
10088
|
}
|
|
8316
10089
|
function parseMilestones(body) {
|
|
8317
10090
|
const milestones = [];
|
|
@@ -8335,7 +10108,7 @@ function parseMilestones(body) {
|
|
|
8335
10108
|
features: featuresResult.value
|
|
8336
10109
|
});
|
|
8337
10110
|
}
|
|
8338
|
-
return (0,
|
|
10111
|
+
return (0, import_types16.Ok)(milestones);
|
|
8339
10112
|
}
|
|
8340
10113
|
function parseFeatures(sectionBody) {
|
|
8341
10114
|
const features = [];
|
|
@@ -8356,7 +10129,7 @@ function parseFeatures(sectionBody) {
|
|
|
8356
10129
|
if (!featureResult.ok) return featureResult;
|
|
8357
10130
|
features.push(featureResult.value);
|
|
8358
10131
|
}
|
|
8359
|
-
return (0,
|
|
10132
|
+
return (0, import_types16.Ok)(features);
|
|
8360
10133
|
}
|
|
8361
10134
|
function parseFeatureFields(name, body) {
|
|
8362
10135
|
const fieldMap = /* @__PURE__ */ new Map();
|
|
@@ -8367,7 +10140,7 @@ function parseFeatureFields(name, body) {
|
|
|
8367
10140
|
}
|
|
8368
10141
|
const statusRaw = fieldMap.get("Status");
|
|
8369
10142
|
if (!statusRaw || !VALID_STATUSES.has(statusRaw)) {
|
|
8370
|
-
return (0,
|
|
10143
|
+
return (0, import_types16.Err)(
|
|
8371
10144
|
new Error(
|
|
8372
10145
|
`Feature "${name}" has invalid status: "${statusRaw ?? "(missing)"}". Valid statuses: ${[...VALID_STATUSES].join(", ")}`
|
|
8373
10146
|
)
|
|
@@ -8381,7 +10154,7 @@ function parseFeatureFields(name, body) {
|
|
|
8381
10154
|
const blockedByRaw = fieldMap.get("Blocked by") ?? EM_DASH;
|
|
8382
10155
|
const blockedBy = blockedByRaw === EM_DASH ? [] : blockedByRaw.split(",").map((b) => b.trim());
|
|
8383
10156
|
const summary = fieldMap.get("Summary") ?? "";
|
|
8384
|
-
return (0,
|
|
10157
|
+
return (0, import_types16.Ok)({ name, status, spec, plans, blockedBy, summary });
|
|
8385
10158
|
}
|
|
8386
10159
|
|
|
8387
10160
|
// src/roadmap/serialize.ts
|
|
@@ -8425,9 +10198,9 @@ function serializeFeature(feature) {
|
|
|
8425
10198
|
}
|
|
8426
10199
|
|
|
8427
10200
|
// src/roadmap/sync.ts
|
|
8428
|
-
var
|
|
10201
|
+
var fs9 = __toESM(require("fs"));
|
|
8429
10202
|
var path9 = __toESM(require("path"));
|
|
8430
|
-
var
|
|
10203
|
+
var import_types17 = require("@harness-engineering/types");
|
|
8431
10204
|
function inferStatus(feature, projectPath, allFeatures) {
|
|
8432
10205
|
if (feature.blockedBy.length > 0) {
|
|
8433
10206
|
const blockerNotDone = feature.blockedBy.some((blockerName) => {
|
|
@@ -8442,9 +10215,9 @@ function inferStatus(feature, projectPath, allFeatures) {
|
|
|
8442
10215
|
const useRootState = featuresWithPlans.length <= 1;
|
|
8443
10216
|
if (useRootState) {
|
|
8444
10217
|
const rootStatePath = path9.join(projectPath, ".harness", "state.json");
|
|
8445
|
-
if (
|
|
10218
|
+
if (fs9.existsSync(rootStatePath)) {
|
|
8446
10219
|
try {
|
|
8447
|
-
const raw =
|
|
10220
|
+
const raw = fs9.readFileSync(rootStatePath, "utf-8");
|
|
8448
10221
|
const state = JSON.parse(raw);
|
|
8449
10222
|
if (state.progress) {
|
|
8450
10223
|
for (const status of Object.values(state.progress)) {
|
|
@@ -8456,15 +10229,15 @@ function inferStatus(feature, projectPath, allFeatures) {
|
|
|
8456
10229
|
}
|
|
8457
10230
|
}
|
|
8458
10231
|
const sessionsDir = path9.join(projectPath, ".harness", "sessions");
|
|
8459
|
-
if (
|
|
10232
|
+
if (fs9.existsSync(sessionsDir)) {
|
|
8460
10233
|
try {
|
|
8461
|
-
const sessionDirs =
|
|
10234
|
+
const sessionDirs = fs9.readdirSync(sessionsDir, { withFileTypes: true });
|
|
8462
10235
|
for (const entry of sessionDirs) {
|
|
8463
10236
|
if (!entry.isDirectory()) continue;
|
|
8464
10237
|
const autopilotPath = path9.join(sessionsDir, entry.name, "autopilot-state.json");
|
|
8465
|
-
if (!
|
|
10238
|
+
if (!fs9.existsSync(autopilotPath)) continue;
|
|
8466
10239
|
try {
|
|
8467
|
-
const raw =
|
|
10240
|
+
const raw = fs9.readFileSync(autopilotPath, "utf-8");
|
|
8468
10241
|
const autopilot = JSON.parse(raw);
|
|
8469
10242
|
if (!autopilot.phases) continue;
|
|
8470
10243
|
const linkedPhases = autopilot.phases.filter(
|
|
@@ -8511,46 +10284,188 @@ function syncRoadmap(options) {
|
|
|
8511
10284
|
to: inferred
|
|
8512
10285
|
});
|
|
8513
10286
|
}
|
|
8514
|
-
return (0,
|
|
10287
|
+
return (0, import_types17.Ok)(changes);
|
|
8515
10288
|
}
|
|
8516
10289
|
|
|
8517
10290
|
// src/interaction/types.ts
|
|
8518
|
-
var
|
|
8519
|
-
var InteractionTypeSchema =
|
|
8520
|
-
var QuestionSchema =
|
|
8521
|
-
text:
|
|
8522
|
-
options:
|
|
8523
|
-
default:
|
|
10291
|
+
var import_zod7 = require("zod");
|
|
10292
|
+
var InteractionTypeSchema = import_zod7.z.enum(["question", "confirmation", "transition"]);
|
|
10293
|
+
var QuestionSchema = import_zod7.z.object({
|
|
10294
|
+
text: import_zod7.z.string(),
|
|
10295
|
+
options: import_zod7.z.array(import_zod7.z.string()).optional(),
|
|
10296
|
+
default: import_zod7.z.string().optional()
|
|
8524
10297
|
});
|
|
8525
|
-
var ConfirmationSchema =
|
|
8526
|
-
text:
|
|
8527
|
-
context:
|
|
10298
|
+
var ConfirmationSchema = import_zod7.z.object({
|
|
10299
|
+
text: import_zod7.z.string(),
|
|
10300
|
+
context: import_zod7.z.string()
|
|
8528
10301
|
});
|
|
8529
|
-
var TransitionSchema =
|
|
8530
|
-
completedPhase:
|
|
8531
|
-
suggestedNext:
|
|
8532
|
-
reason:
|
|
8533
|
-
artifacts:
|
|
8534
|
-
requiresConfirmation:
|
|
8535
|
-
summary:
|
|
10302
|
+
var TransitionSchema = import_zod7.z.object({
|
|
10303
|
+
completedPhase: import_zod7.z.string(),
|
|
10304
|
+
suggestedNext: import_zod7.z.string(),
|
|
10305
|
+
reason: import_zod7.z.string(),
|
|
10306
|
+
artifacts: import_zod7.z.array(import_zod7.z.string()),
|
|
10307
|
+
requiresConfirmation: import_zod7.z.boolean(),
|
|
10308
|
+
summary: import_zod7.z.string()
|
|
8536
10309
|
});
|
|
8537
|
-
var EmitInteractionInputSchema =
|
|
8538
|
-
path:
|
|
10310
|
+
var EmitInteractionInputSchema = import_zod7.z.object({
|
|
10311
|
+
path: import_zod7.z.string(),
|
|
8539
10312
|
type: InteractionTypeSchema,
|
|
8540
|
-
stream:
|
|
10313
|
+
stream: import_zod7.z.string().optional(),
|
|
8541
10314
|
question: QuestionSchema.optional(),
|
|
8542
10315
|
confirmation: ConfirmationSchema.optional(),
|
|
8543
10316
|
transition: TransitionSchema.optional()
|
|
8544
10317
|
});
|
|
8545
10318
|
|
|
8546
|
-
// src/
|
|
8547
|
-
var
|
|
10319
|
+
// src/blueprint/scanner.ts
|
|
10320
|
+
var fs10 = __toESM(require("fs/promises"));
|
|
8548
10321
|
var path10 = __toESM(require("path"));
|
|
10322
|
+
var ProjectScanner = class {
|
|
10323
|
+
constructor(rootDir) {
|
|
10324
|
+
this.rootDir = rootDir;
|
|
10325
|
+
}
|
|
10326
|
+
async scan() {
|
|
10327
|
+
let projectName = path10.basename(this.rootDir);
|
|
10328
|
+
try {
|
|
10329
|
+
const pkgPath = path10.join(this.rootDir, "package.json");
|
|
10330
|
+
const pkgRaw = await fs10.readFile(pkgPath, "utf-8");
|
|
10331
|
+
const pkg = JSON.parse(pkgRaw);
|
|
10332
|
+
if (pkg.name) projectName = pkg.name;
|
|
10333
|
+
} catch {
|
|
10334
|
+
}
|
|
10335
|
+
return {
|
|
10336
|
+
projectName,
|
|
10337
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
10338
|
+
modules: [
|
|
10339
|
+
{
|
|
10340
|
+
id: "foundations",
|
|
10341
|
+
title: "Foundations",
|
|
10342
|
+
description: "Utility files and basic types.",
|
|
10343
|
+
files: []
|
|
10344
|
+
},
|
|
10345
|
+
{
|
|
10346
|
+
id: "core-logic",
|
|
10347
|
+
title: "Core Logic",
|
|
10348
|
+
description: "Mid-level services and domain logic.",
|
|
10349
|
+
files: []
|
|
10350
|
+
},
|
|
10351
|
+
{
|
|
10352
|
+
id: "interaction-surface",
|
|
10353
|
+
title: "Interaction Surface",
|
|
10354
|
+
description: "APIs, CLIs, and Entry Points.",
|
|
10355
|
+
files: []
|
|
10356
|
+
},
|
|
10357
|
+
{
|
|
10358
|
+
id: "cross-cutting",
|
|
10359
|
+
title: "Cross-Cutting Concerns",
|
|
10360
|
+
description: "Security, Logging, and Observability.",
|
|
10361
|
+
files: []
|
|
10362
|
+
}
|
|
10363
|
+
],
|
|
10364
|
+
hotspots: [],
|
|
10365
|
+
dependencies: []
|
|
10366
|
+
};
|
|
10367
|
+
}
|
|
10368
|
+
};
|
|
10369
|
+
|
|
10370
|
+
// src/blueprint/generator.ts
|
|
10371
|
+
var fs11 = __toESM(require("fs/promises"));
|
|
10372
|
+
var path11 = __toESM(require("path"));
|
|
10373
|
+
var ejs = __toESM(require("ejs"));
|
|
10374
|
+
|
|
10375
|
+
// src/blueprint/templates.ts
|
|
10376
|
+
var SHELL_TEMPLATE = `
|
|
10377
|
+
<!DOCTYPE html>
|
|
10378
|
+
<html lang="en">
|
|
10379
|
+
<head>
|
|
10380
|
+
<meta charset="UTF-8">
|
|
10381
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
10382
|
+
<title>Blueprint: <%= projectName %></title>
|
|
10383
|
+
<style><%- styles %></style>
|
|
10384
|
+
</head>
|
|
10385
|
+
<body>
|
|
10386
|
+
<div id="app">
|
|
10387
|
+
<header>
|
|
10388
|
+
<h1>Blueprint: <%= projectName %></h1>
|
|
10389
|
+
<p>Generated at: <%= generatedAt %></p>
|
|
10390
|
+
</header>
|
|
10391
|
+
<main>
|
|
10392
|
+
<section class="modules">
|
|
10393
|
+
<% modules.forEach(module => { %>
|
|
10394
|
+
<article class="module" id="<%= module.id %>">
|
|
10395
|
+
<h2><%= module.title %></h2>
|
|
10396
|
+
<p><%= module.description %></p>
|
|
10397
|
+
<div class="content">
|
|
10398
|
+
<h3>Code Translation</h3>
|
|
10399
|
+
<div class="translation"><%- module.content.codeTranslation %></div>
|
|
10400
|
+
</div>
|
|
10401
|
+
</article>
|
|
10402
|
+
|
|
10403
|
+
<% }) %>
|
|
10404
|
+
</section>
|
|
10405
|
+
</main>
|
|
10406
|
+
</div>
|
|
10407
|
+
<script><%- scripts %></script>
|
|
10408
|
+
</body>
|
|
10409
|
+
</html>
|
|
10410
|
+
`;
|
|
10411
|
+
var STYLES = `
|
|
10412
|
+
body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; line-height: 1.6; margin: 0; padding: 20px; color: #333; }
|
|
10413
|
+
header { border-bottom: 2px solid #eee; margin-bottom: 20px; padding-bottom: 10px; }
|
|
10414
|
+
.module { background: #f9f9f9; border: 1px solid #ddd; padding: 15px; margin-bottom: 15px; border-radius: 4px; }
|
|
10415
|
+
.module h2 { margin-top: 0; color: #0066cc; }
|
|
10416
|
+
`;
|
|
10417
|
+
var SCRIPTS = `
|
|
10418
|
+
console.log('Blueprint player initialized.');
|
|
10419
|
+
`;
|
|
10420
|
+
|
|
10421
|
+
// src/shared/llm.ts
|
|
10422
|
+
var MockLLMService = class {
|
|
10423
|
+
async generate(prompt) {
|
|
10424
|
+
return "This is a mock LLM response for: " + prompt;
|
|
10425
|
+
}
|
|
10426
|
+
};
|
|
10427
|
+
var llmService = new MockLLMService();
|
|
10428
|
+
|
|
10429
|
+
// src/blueprint/content-pipeline.ts
|
|
10430
|
+
var ContentPipeline = class {
|
|
10431
|
+
async generateModuleContent(module2) {
|
|
10432
|
+
const codeContext = module2.files.join("\n");
|
|
10433
|
+
const translation = await llmService.generate(
|
|
10434
|
+
`You are a technical educator. Explain the following code clearly and concisely: ${codeContext}`
|
|
10435
|
+
);
|
|
10436
|
+
return {
|
|
10437
|
+
codeTranslation: translation
|
|
10438
|
+
};
|
|
10439
|
+
}
|
|
10440
|
+
};
|
|
10441
|
+
|
|
10442
|
+
// src/blueprint/generator.ts
|
|
10443
|
+
var BlueprintGenerator = class {
|
|
10444
|
+
contentPipeline = new ContentPipeline();
|
|
10445
|
+
async generate(data, options) {
|
|
10446
|
+
await Promise.all(
|
|
10447
|
+
data.modules.map(async (module2) => {
|
|
10448
|
+
module2.content = await this.contentPipeline.generateModuleContent(module2);
|
|
10449
|
+
})
|
|
10450
|
+
);
|
|
10451
|
+
const html = ejs.render(SHELL_TEMPLATE, {
|
|
10452
|
+
...data,
|
|
10453
|
+
styles: STYLES,
|
|
10454
|
+
scripts: SCRIPTS
|
|
10455
|
+
});
|
|
10456
|
+
await fs11.mkdir(options.outputDir, { recursive: true });
|
|
10457
|
+
await fs11.writeFile(path11.join(options.outputDir, "index.html"), html);
|
|
10458
|
+
}
|
|
10459
|
+
};
|
|
10460
|
+
|
|
10461
|
+
// src/update-checker.ts
|
|
10462
|
+
var fs12 = __toESM(require("fs"));
|
|
10463
|
+
var path12 = __toESM(require("path"));
|
|
8549
10464
|
var os = __toESM(require("os"));
|
|
8550
10465
|
var import_child_process3 = require("child_process");
|
|
8551
10466
|
function getStatePath() {
|
|
8552
10467
|
const home = process.env["HOME"] || os.homedir();
|
|
8553
|
-
return
|
|
10468
|
+
return path12.join(home, ".harness", "update-check.json");
|
|
8554
10469
|
}
|
|
8555
10470
|
function isUpdateCheckEnabled(configInterval) {
|
|
8556
10471
|
if (process.env["HARNESS_NO_UPDATE_CHECK"] === "1") return false;
|
|
@@ -8563,7 +10478,7 @@ function shouldRunCheck(state, intervalMs) {
|
|
|
8563
10478
|
}
|
|
8564
10479
|
function readCheckState() {
|
|
8565
10480
|
try {
|
|
8566
|
-
const raw =
|
|
10481
|
+
const raw = fs12.readFileSync(getStatePath(), "utf-8");
|
|
8567
10482
|
const parsed = JSON.parse(raw);
|
|
8568
10483
|
if (typeof parsed === "object" && parsed !== null && "lastCheckTime" in parsed && typeof parsed.lastCheckTime === "number" && "currentVersion" in parsed && typeof parsed.currentVersion === "string") {
|
|
8569
10484
|
const state = parsed;
|
|
@@ -8580,7 +10495,7 @@ function readCheckState() {
|
|
|
8580
10495
|
}
|
|
8581
10496
|
function spawnBackgroundCheck(currentVersion) {
|
|
8582
10497
|
const statePath = getStatePath();
|
|
8583
|
-
const stateDir =
|
|
10498
|
+
const stateDir = path12.dirname(statePath);
|
|
8584
10499
|
const script = `
|
|
8585
10500
|
const { execSync } = require('child_process');
|
|
8586
10501
|
const fs = require('fs');
|
|
@@ -8634,39 +10549,64 @@ Run "harness update" to upgrade.`;
|
|
|
8634
10549
|
}
|
|
8635
10550
|
|
|
8636
10551
|
// src/index.ts
|
|
8637
|
-
var VERSION = "
|
|
10552
|
+
var VERSION = "0.11.0";
|
|
8638
10553
|
// Annotate the CommonJS export names for ESM import in node:
|
|
8639
10554
|
0 && (module.exports = {
|
|
8640
10555
|
AGENT_DESCRIPTORS,
|
|
8641
10556
|
ARCHITECTURE_DESCRIPTOR,
|
|
8642
10557
|
AgentActionEmitter,
|
|
10558
|
+
ArchBaselineManager,
|
|
10559
|
+
ArchBaselineSchema,
|
|
10560
|
+
ArchConfigSchema,
|
|
10561
|
+
ArchDiffResultSchema,
|
|
10562
|
+
ArchMetricCategorySchema,
|
|
8643
10563
|
BUG_DETECTION_DESCRIPTOR,
|
|
8644
10564
|
BaselineManager,
|
|
8645
10565
|
BenchmarkRunner,
|
|
10566
|
+
BlueprintGenerator,
|
|
10567
|
+
BundleConstraintsSchema,
|
|
10568
|
+
BundleSchema,
|
|
8646
10569
|
COMPLIANCE_DESCRIPTOR,
|
|
10570
|
+
CategoryBaselineSchema,
|
|
10571
|
+
CategoryRegressionSchema,
|
|
8647
10572
|
ChecklistBuilder,
|
|
10573
|
+
CircularDepsCollector,
|
|
10574
|
+
ComplexityCollector,
|
|
8648
10575
|
ConfirmationSchema,
|
|
8649
10576
|
ConsoleSink,
|
|
10577
|
+
ConstraintRuleSchema,
|
|
10578
|
+
ContentPipeline,
|
|
10579
|
+
ContributionsSchema,
|
|
10580
|
+
CouplingCollector,
|
|
8650
10581
|
CriticalPathResolver,
|
|
8651
10582
|
DEFAULT_PROVIDER_TIERS,
|
|
8652
10583
|
DEFAULT_SECURITY_CONFIG,
|
|
8653
10584
|
DEFAULT_STATE,
|
|
8654
10585
|
DEFAULT_STREAM_INDEX,
|
|
10586
|
+
DepDepthCollector,
|
|
8655
10587
|
EmitInteractionInputSchema,
|
|
8656
10588
|
EntropyAnalyzer,
|
|
8657
10589
|
EntropyConfigSchema,
|
|
8658
10590
|
ExclusionSet,
|
|
8659
10591
|
FailureEntrySchema,
|
|
8660
10592
|
FileSink,
|
|
10593
|
+
ForbiddenImportCollector,
|
|
8661
10594
|
GateConfigSchema,
|
|
8662
10595
|
GateResultSchema,
|
|
8663
10596
|
HandoffSchema,
|
|
8664
10597
|
HarnessStateSchema,
|
|
8665
10598
|
InteractionTypeSchema,
|
|
10599
|
+
LayerViolationCollector,
|
|
10600
|
+
LockfilePackageSchema,
|
|
10601
|
+
LockfileSchema,
|
|
10602
|
+
ManifestSchema,
|
|
10603
|
+
MetricResultSchema,
|
|
10604
|
+
ModuleSizeCollector,
|
|
8666
10605
|
NoOpExecutor,
|
|
8667
10606
|
NoOpSink,
|
|
8668
10607
|
NoOpTelemetryAdapter,
|
|
8669
10608
|
PatternConfigSchema,
|
|
10609
|
+
ProjectScanner,
|
|
8670
10610
|
QuestionSchema,
|
|
8671
10611
|
REQUIRED_SECTIONS,
|
|
8672
10612
|
RegressionDetector,
|
|
@@ -8674,16 +10614,26 @@ var VERSION = "1.8.2";
|
|
|
8674
10614
|
SECURITY_DESCRIPTOR,
|
|
8675
10615
|
SecurityConfigSchema,
|
|
8676
10616
|
SecurityScanner,
|
|
10617
|
+
SharableBoundaryConfigSchema,
|
|
10618
|
+
SharableForbiddenImportSchema,
|
|
10619
|
+
SharableLayerSchema,
|
|
10620
|
+
SharableSecurityRulesSchema,
|
|
8677
10621
|
StreamIndexSchema,
|
|
8678
10622
|
StreamInfoSchema,
|
|
10623
|
+
ThresholdConfigSchema,
|
|
8679
10624
|
TransitionSchema,
|
|
8680
10625
|
TypeScriptParser,
|
|
8681
10626
|
VERSION,
|
|
10627
|
+
ViolationSchema,
|
|
10628
|
+
addProvenance,
|
|
8682
10629
|
analyzeDiff,
|
|
8683
10630
|
appendFailure,
|
|
8684
10631
|
appendLearning,
|
|
8685
10632
|
applyFixes,
|
|
8686
10633
|
applyHotspotDowngrade,
|
|
10634
|
+
archMatchers,
|
|
10635
|
+
archModule,
|
|
10636
|
+
architecture,
|
|
8687
10637
|
archiveFailures,
|
|
8688
10638
|
archiveStream,
|
|
8689
10639
|
buildDependencyGraph,
|
|
@@ -8693,6 +10643,7 @@ var VERSION = "1.8.2";
|
|
|
8693
10643
|
checkEligibility,
|
|
8694
10644
|
classifyFinding,
|
|
8695
10645
|
configureFeedback,
|
|
10646
|
+
constraintRuleId,
|
|
8696
10647
|
contextBudget,
|
|
8697
10648
|
contextFilter,
|
|
8698
10649
|
createBoundaryValidator,
|
|
@@ -8707,6 +10658,8 @@ var VERSION = "1.8.2";
|
|
|
8707
10658
|
cryptoRules,
|
|
8708
10659
|
deduplicateCleanupFindings,
|
|
8709
10660
|
deduplicateFindings,
|
|
10661
|
+
deepMergeConstraints,
|
|
10662
|
+
defaultCollectors,
|
|
8710
10663
|
defineLayer,
|
|
8711
10664
|
deserializationRules,
|
|
8712
10665
|
detectChangeType,
|
|
@@ -8719,9 +10672,12 @@ var VERSION = "1.8.2";
|
|
|
8719
10672
|
detectPatternViolations,
|
|
8720
10673
|
detectSizeBudgetViolations,
|
|
8721
10674
|
detectStack,
|
|
10675
|
+
detectStaleConstraints,
|
|
8722
10676
|
determineAssessment,
|
|
10677
|
+
diff,
|
|
8723
10678
|
executeWorkflow,
|
|
8724
10679
|
expressRules,
|
|
10680
|
+
extractBundle,
|
|
8725
10681
|
extractMarkdownLinks,
|
|
8726
10682
|
extractSections,
|
|
8727
10683
|
fanOutReview,
|
|
@@ -8752,6 +10708,7 @@ var VERSION = "1.8.2";
|
|
|
8752
10708
|
networkRules,
|
|
8753
10709
|
nodeRules,
|
|
8754
10710
|
parseDiff,
|
|
10711
|
+
parseManifest,
|
|
8755
10712
|
parseRoadmap,
|
|
8756
10713
|
parseSecurityConfig,
|
|
8757
10714
|
parseSize,
|
|
@@ -8759,6 +10716,9 @@ var VERSION = "1.8.2";
|
|
|
8759
10716
|
previewFix,
|
|
8760
10717
|
reactRules,
|
|
8761
10718
|
readCheckState,
|
|
10719
|
+
readLockfile,
|
|
10720
|
+
removeContributions,
|
|
10721
|
+
removeProvenance,
|
|
8762
10722
|
requestMultiplePeerReviews,
|
|
8763
10723
|
requestPeerReview,
|
|
8764
10724
|
resetFeedbackConfig,
|
|
@@ -8766,6 +10726,8 @@ var VERSION = "1.8.2";
|
|
|
8766
10726
|
resolveModelTier,
|
|
8767
10727
|
resolveRuleSeverity,
|
|
8768
10728
|
resolveStreamPath,
|
|
10729
|
+
resolveThresholds,
|
|
10730
|
+
runAll,
|
|
8769
10731
|
runArchitectureAgent,
|
|
8770
10732
|
runBugDetectionAgent,
|
|
8771
10733
|
runCIChecks,
|
|
@@ -8785,6 +10747,7 @@ var VERSION = "1.8.2";
|
|
|
8785
10747
|
setActiveStream,
|
|
8786
10748
|
shouldRunCheck,
|
|
8787
10749
|
spawnBackgroundCheck,
|
|
10750
|
+
syncConstraintNodes,
|
|
8788
10751
|
syncRoadmap,
|
|
8789
10752
|
touchStream,
|
|
8790
10753
|
trackAction,
|
|
@@ -8797,6 +10760,9 @@ var VERSION = "1.8.2";
|
|
|
8797
10760
|
validateFindings,
|
|
8798
10761
|
validateKnowledgeMap,
|
|
8799
10762
|
validatePatternConfig,
|
|
10763
|
+
violationId,
|
|
10764
|
+
writeConfig,
|
|
10765
|
+
writeLockfile,
|
|
8800
10766
|
xssRules,
|
|
8801
10767
|
...require("@harness-engineering/types")
|
|
8802
10768
|
});
|