hadara 0.1.0-rc.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.
Files changed (121) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +109 -0
  3. package/dist/agent/evidence.js +50 -0
  4. package/dist/agent/loop.js +124 -0
  5. package/dist/cli/args.js +70 -0
  6. package/dist/cli/dashboard.js +185 -0
  7. package/dist/cli/debt.js +41 -0
  8. package/dist/cli/doctor.js +68 -0
  9. package/dist/cli/errors.js +58 -0
  10. package/dist/cli/evidence-json.js +75 -0
  11. package/dist/cli/evidence.js +80 -0
  12. package/dist/cli/handoff.js +16 -0
  13. package/dist/cli/harness.js +57 -0
  14. package/dist/cli/hermes-json.js +31 -0
  15. package/dist/cli/hermes.js +28 -0
  16. package/dist/cli/init.js +142 -0
  17. package/dist/cli/install.js +34 -0
  18. package/dist/cli/main.js +216 -0
  19. package/dist/cli/mcp.js +15 -0
  20. package/dist/cli/package-smoke.js +37 -0
  21. package/dist/cli/policy-json.js +22 -0
  22. package/dist/cli/policy.js +43 -0
  23. package/dist/cli/release-artifact.js +47 -0
  24. package/dist/cli/release-dry-run.js +24 -0
  25. package/dist/cli/release-gate.js +28 -0
  26. package/dist/cli/release-publish.js +41 -0
  27. package/dist/cli/run-scaffold.js +68 -0
  28. package/dist/cli/run-state.js +41 -0
  29. package/dist/cli/run.js +191 -0
  30. package/dist/cli/smoke.js +58 -0
  31. package/dist/cli/status-json.js +6 -0
  32. package/dist/cli/status.js +26 -0
  33. package/dist/cli/task-json.js +8 -0
  34. package/dist/cli/task.js +64 -0
  35. package/dist/cli/tools.js +25 -0
  36. package/dist/cli/tui.js +72 -0
  37. package/dist/cli/write-preflight.js +27 -0
  38. package/dist/core/audit.js +41 -0
  39. package/dist/core/events.js +63 -0
  40. package/dist/core/fs.js +44 -0
  41. package/dist/core/paths.js +59 -0
  42. package/dist/core/redaction.js +178 -0
  43. package/dist/core/schema.js +253 -0
  44. package/dist/core/workspace.js +47 -0
  45. package/dist/evidence/evidence.js +170 -0
  46. package/dist/evidence/private-manifest.js +101 -0
  47. package/dist/handoff/handoff.js +49 -0
  48. package/dist/harness/replay.js +200 -0
  49. package/dist/harness/validate.js +465 -0
  50. package/dist/hermes/context-export.js +104 -0
  51. package/dist/index.js +29 -0
  52. package/dist/mcp/server.js +104 -0
  53. package/dist/mcp/tool-dispatch.js +159 -0
  54. package/dist/mcp/tool-registry.js +150 -0
  55. package/dist/mcp/tool-schemas.js +18 -0
  56. package/dist/policy/command-risk.js +39 -0
  57. package/dist/policy/permission-matrix.js +42 -0
  58. package/dist/policy/policy.js +20 -0
  59. package/dist/policy/preflight.js +47 -0
  60. package/dist/policy/presets.js +24 -0
  61. package/dist/policy/tokenizer.js +53 -0
  62. package/dist/providers/fallback-executor.js +46 -0
  63. package/dist/providers/mock-provider.js +49 -0
  64. package/dist/providers/provider-contract.js +2 -0
  65. package/dist/providers/provider-preparation.js +220 -0
  66. package/dist/providers/scripted-provider.js +69 -0
  67. package/dist/schemas/active-run-projection.schema.json +73 -0
  68. package/dist/schemas/active-run-resume.schema.json +68 -0
  69. package/dist/schemas/clean-checkout-smoke.schema.json +126 -0
  70. package/dist/schemas/context-export.schema.json +35 -0
  71. package/dist/schemas/event.schema.json +17 -0
  72. package/dist/schemas/evidence-list.schema.json +49 -0
  73. package/dist/schemas/feature-smoke.schema.json +67 -0
  74. package/dist/schemas/install-plan.schema.json +93 -0
  75. package/dist/schemas/package-smoke.schema.json +130 -0
  76. package/dist/schemas/private-evidence.schema.json +48 -0
  77. package/dist/schemas/provider-call.schema.json +42 -0
  78. package/dist/schemas/provider-config.schema.json +43 -0
  79. package/dist/schemas/release-artifact-manifest.schema.json +55 -0
  80. package/dist/schemas/release-artifact.schema.json +140 -0
  81. package/dist/schemas/release-dry-run.schema.json +141 -0
  82. package/dist/schemas/release-gate.schema.json +42 -0
  83. package/dist/schemas/release-publish.schema.json +114 -0
  84. package/dist/schemas/schema-index.json +145 -0
  85. package/dist/schemas/smoke-evidence-summary.schema.json +88 -0
  86. package/dist/schemas/tools-list.schema.json +78 -0
  87. package/dist/schemas/write-preflight.schema.json +47 -0
  88. package/dist/services/active-run-state.js +215 -0
  89. package/dist/services/capability-registry.js +540 -0
  90. package/dist/services/clean-checkout-smoke.js +393 -0
  91. package/dist/services/evidence-list.js +136 -0
  92. package/dist/services/feature-smoke.js +155 -0
  93. package/dist/services/harness-service.js +7 -0
  94. package/dist/services/install-plan.js +233 -0
  95. package/dist/services/operational-debt.js +767 -0
  96. package/dist/services/operations-status-service.js +195 -0
  97. package/dist/services/package-smoke.js +676 -0
  98. package/dist/services/policy-service.js +25 -0
  99. package/dist/services/project-read-model.js +101 -0
  100. package/dist/services/release-artifact-evidence.js +77 -0
  101. package/dist/services/release-artifact.js +351 -0
  102. package/dist/services/release-dry-run.js +253 -0
  103. package/dist/services/release-evidence.js +138 -0
  104. package/dist/services/release-publish.js +163 -0
  105. package/dist/services/smoke-evidence.js +104 -0
  106. package/dist/services/task-read-model.js +125 -0
  107. package/dist/services/tools-list.js +26 -0
  108. package/dist/services/write-preflight.js +240 -0
  109. package/dist/task/task-capsule.js +121 -0
  110. package/dist/tools/fake-shell.js +56 -0
  111. package/dist/tui/cache.js +341 -0
  112. package/dist/tui/constants.js +44 -0
  113. package/dist/tui/layout.js +140 -0
  114. package/dist/tui/markdown.js +238 -0
  115. package/dist/tui/read-model-worker.js +24 -0
  116. package/dist/tui/read-model.js +502 -0
  117. package/dist/tui/snapshot.js +434 -0
  118. package/dist/tui/state.js +229 -0
  119. package/dist/tui/terminal.js +475 -0
  120. package/dist/tui/theme.js +86 -0
  121. package/package.json +16 -0
@@ -0,0 +1,59 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.resolveHadaraPaths = resolveHadaraPaths;
7
+ exports.isInside = isInside;
8
+ exports.normalizeHadaraPath = normalizeHadaraPath;
9
+ exports.assertProjectStoreBoundary = assertProjectStoreBoundary;
10
+ const node_fs_1 = __importDefault(require("node:fs"));
11
+ const node_path_1 = __importDefault(require("node:path"));
12
+ function resolveHadaraPaths(input = {}) {
13
+ const projectRoot = normalizeHadaraPath(input.projectRoot ?? process.env.HADARA_PROJECT_ROOT ?? process.cwd());
14
+ const portableRoot = normalizeHadaraPath(input.portableRoot ?? process.env.HADARA_HOME ?? node_path_1.default.join(projectRoot, '.hadara', 'local', 'portable'));
15
+ const dataRoot = node_path_1.default.join(portableRoot, 'data');
16
+ const projectHadaraDir = node_path_1.default.join(projectRoot, '.hadara');
17
+ assertProjectStoreBoundary({ dataRoot, projectRoot });
18
+ return {
19
+ portableRoot,
20
+ dataRoot,
21
+ configDir: node_path_1.default.join(dataRoot, 'config'),
22
+ secretsDir: node_path_1.default.join(dataRoot, 'secrets'),
23
+ sessionsDir: node_path_1.default.join(dataRoot, 'sessions'),
24
+ logsDir: node_path_1.default.join(dataRoot, 'logs'),
25
+ auditDir: node_path_1.default.join(dataRoot, 'audit'),
26
+ exportsDir: node_path_1.default.join(dataRoot, 'exports'),
27
+ projectRoot,
28
+ projectHadaraDir,
29
+ projectDocsDir: node_path_1.default.join(projectRoot, 'docs'),
30
+ projectTasksDir: node_path_1.default.join(projectRoot, 'tasks'),
31
+ projectContextDir: node_path_1.default.join(projectHadaraDir, 'context')
32
+ };
33
+ }
34
+ function isInside(parent, child) {
35
+ const normalizedParent = normalizeHadaraPath(parent);
36
+ const normalizedChild = normalizeHadaraPath(child);
37
+ const relative = node_path_1.default.relative(realpathIfExists(normalizedParent), realpathIfExists(normalizedChild));
38
+ return relative === '' || (!relative.startsWith('..') && !node_path_1.default.isAbsolute(relative));
39
+ }
40
+ function normalizeHadaraPath(value) {
41
+ if (/^[A-Za-z]:[\\/]/.test(value)) {
42
+ return node_path_1.default.win32.normalize(value);
43
+ }
44
+ return node_path_1.default.resolve(value);
45
+ }
46
+ function assertProjectStoreBoundary(paths) {
47
+ const projectDataDir = node_path_1.default.join(paths.projectRoot, 'data');
48
+ if (isInside(projectDataDir, paths.dataRoot)) {
49
+ throw new Error('HADARA dataRoot must not use projectRoot/data. Set HADARA_HOME outside the repo or use .hadara/local.');
50
+ }
51
+ }
52
+ function realpathIfExists(value) {
53
+ try {
54
+ return node_fs_1.default.realpathSync.native(value);
55
+ }
56
+ catch {
57
+ return value;
58
+ }
59
+ }
@@ -0,0 +1,178 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.REDACTION_PATTERNS = void 0;
4
+ exports.redactSecrets = redactSecrets;
5
+ exports.containsSecret = containsSecret;
6
+ exports.hasBlockingRedactionFinding = hasBlockingRedactionFinding;
7
+ exports.createRedactionReport = createRedactionReport;
8
+ exports.REDACTION_PATTERNS = [
9
+ {
10
+ id: 'openai-api-key',
11
+ description: 'OpenAI-style API key',
12
+ regex: /sk-[A-Za-z0-9_-]{16,}/g,
13
+ severity: 'critical',
14
+ replacement: '[REDACTED]',
15
+ enabledByDefault: true
16
+ },
17
+ {
18
+ id: 'slack-token',
19
+ description: 'Slack token',
20
+ regex: /xox[baprs]-[A-Za-z0-9-]{10,}/g,
21
+ severity: 'high',
22
+ replacement: '[REDACTED]',
23
+ enabledByDefault: true
24
+ },
25
+ {
26
+ id: 'aws-access-key-id',
27
+ description: 'AWS access key id',
28
+ regex: /AKIA[0-9A-Z]{16}/g,
29
+ severity: 'high',
30
+ replacement: '[REDACTED]',
31
+ enabledByDefault: true
32
+ },
33
+ {
34
+ id: 'aws-secret-access-key',
35
+ description: 'AWS secret access key assignment',
36
+ regex: /(aws[_-]?secret[_-]?access[_-]?key\s*=\s*)[A-Za-z0-9/+=]{32,}/gi,
37
+ severity: 'critical',
38
+ replacement: '$1[REDACTED]',
39
+ enabledByDefault: true
40
+ },
41
+ {
42
+ id: 'github-token',
43
+ description: 'GitHub personal access token',
44
+ regex: /(?:ghp|gho|ghu|ghs|ghr)_[A-Za-z0-9_]{20,}|github_pat_[A-Za-z0-9_]{20,}/g,
45
+ severity: 'critical',
46
+ replacement: '[REDACTED]',
47
+ enabledByDefault: true
48
+ },
49
+ {
50
+ id: 'gitlab-token',
51
+ description: 'GitLab personal access token',
52
+ regex: /glpat-[A-Za-z0-9_-]{20,}/g,
53
+ severity: 'critical',
54
+ replacement: '[REDACTED]',
55
+ enabledByDefault: true
56
+ },
57
+ {
58
+ id: 'google-api-key',
59
+ description: 'Google API key',
60
+ regex: /AIza[0-9A-Za-z_-]{35}/g,
61
+ severity: 'critical',
62
+ replacement: '[REDACTED]',
63
+ enabledByDefault: true
64
+ },
65
+ {
66
+ id: 'private-key-block',
67
+ description: 'Private key block',
68
+ regex: /-----BEGIN [A-Z ]*PRIVATE KEY-----[\s\S]*?-----END [A-Z ]*PRIVATE KEY-----/g,
69
+ severity: 'critical',
70
+ replacement: '[REDACTED]',
71
+ enabledByDefault: true
72
+ },
73
+ {
74
+ id: 'openssh-private-key',
75
+ description: 'OpenSSH private key payload',
76
+ regex: /openssh-key-v1[A-Za-z0-9+/=\s-]*/gi,
77
+ severity: 'critical',
78
+ replacement: '[REDACTED]',
79
+ enabledByDefault: true
80
+ },
81
+ {
82
+ id: 'jwt',
83
+ description: 'JWT-like bearer token',
84
+ regex: /eyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}/g,
85
+ severity: 'high',
86
+ replacement: '[REDACTED]',
87
+ enabledByDefault: true
88
+ },
89
+ {
90
+ id: 'azure-connection-string',
91
+ description: 'Azure storage connection string with account key',
92
+ regex: /DefaultEndpointsProtocol=[^;\s]+;AccountName=[^;\s]+;AccountKey=[^;\s]+(?:;EndpointSuffix=[^;\s]+)?/gi,
93
+ severity: 'critical',
94
+ replacement: '[REDACTED]',
95
+ enabledByDefault: true
96
+ },
97
+ {
98
+ id: 'npm-token',
99
+ description: 'npm access token',
100
+ regex: /npm_[A-Za-z0-9]{20,}/g,
101
+ severity: 'critical',
102
+ replacement: '[REDACTED]',
103
+ enabledByDefault: true
104
+ },
105
+ {
106
+ id: 'sensitive-assignment',
107
+ description: 'Sensitive key assignment',
108
+ regex: /((?:api[_-]?key|token|password|secret|private[_-]?key)\s*=\s*)[^\s]+/gi,
109
+ severity: 'high',
110
+ replacement: '$1[REDACTED]',
111
+ enabledByDefault: true
112
+ },
113
+ {
114
+ id: 'authorization-bearer',
115
+ description: 'Authorization bearer token',
116
+ regex: /(authorization:\s*bearer\s+)[^\s]+/gi,
117
+ severity: 'high',
118
+ replacement: '$1[REDACTED]',
119
+ enabledByDefault: true
120
+ }
121
+ ];
122
+ function redactSecrets(input) {
123
+ return createRedactionReport(input, { includeRedactedText: true }).redactedText ?? input;
124
+ }
125
+ function containsSecret(input) {
126
+ return createRedactionReport(input).findings.length > 0;
127
+ }
128
+ function hasBlockingRedactionFinding(report, minimumSeverity = 'high') {
129
+ const minimumRank = redactionSeverityRank(minimumSeverity);
130
+ return report.findings.some((finding) => redactionSeverityRank(finding.severity) >= minimumRank);
131
+ }
132
+ function createRedactionReport(input, options = {}) {
133
+ const enabledPatterns = (options.patterns ?? exports.REDACTION_PATTERNS).filter((pattern) => pattern.enabledByDefault);
134
+ const findings = scanRedactionFindings(input, enabledPatterns);
135
+ const redactedText = options.includeRedactedText ? redactWithPatterns(input, enabledPatterns) : undefined;
136
+ const output = redactedText ?? input;
137
+ return {
138
+ schemaVersion: 'hadara.redaction.report.v1',
139
+ ok: findings.length === 0,
140
+ inputBytes: Buffer.byteLength(input, 'utf8'),
141
+ outputBytes: Buffer.byteLength(output, 'utf8'),
142
+ findings,
143
+ ...(options.includeRedactedText ? { redactedText: output } : {})
144
+ };
145
+ }
146
+ function scanRedactionFindings(input, patterns) {
147
+ const findings = [];
148
+ for (const pattern of patterns) {
149
+ const regex = cloneGlobalRegex(pattern.regex);
150
+ const matches = input.match(regex);
151
+ if (matches?.length) {
152
+ findings.push({
153
+ patternId: pattern.id,
154
+ severity: pattern.severity,
155
+ count: matches.length
156
+ });
157
+ }
158
+ }
159
+ return findings;
160
+ }
161
+ function redactWithPatterns(input, patterns) {
162
+ return patterns.reduce((text, pattern) => text.replace(cloneGlobalRegex(pattern.regex), pattern.replacement), input);
163
+ }
164
+ function redactionSeverityRank(severity) {
165
+ switch (severity) {
166
+ case 'low':
167
+ return 1;
168
+ case 'medium':
169
+ return 2;
170
+ case 'high':
171
+ return 3;
172
+ case 'critical':
173
+ return 4;
174
+ }
175
+ }
176
+ function cloneGlobalRegex(regex) {
177
+ return new RegExp(regex.source, regex.flags.includes('g') ? regex.flags : `${regex.flags}g`);
178
+ }
@@ -0,0 +1,253 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.SchemaValidationError = void 0;
7
+ exports.loadSchema = loadSchema;
8
+ exports.validateSchema = validateSchema;
9
+ exports.assertSchema = assertSchema;
10
+ const schema_index_json_1 = __importDefault(require("../schemas/schema-index.json"));
11
+ const active_run_projection_schema_json_1 = __importDefault(require("../schemas/active-run-projection.schema.json"));
12
+ const active_run_resume_schema_json_1 = __importDefault(require("../schemas/active-run-resume.schema.json"));
13
+ const clean_checkout_smoke_schema_json_1 = __importDefault(require("../schemas/clean-checkout-smoke.schema.json"));
14
+ const context_export_schema_json_1 = __importDefault(require("../schemas/context-export.schema.json"));
15
+ const evidence_list_schema_json_1 = __importDefault(require("../schemas/evidence-list.schema.json"));
16
+ const event_schema_json_1 = __importDefault(require("../schemas/event.schema.json"));
17
+ const feature_smoke_schema_json_1 = __importDefault(require("../schemas/feature-smoke.schema.json"));
18
+ const install_plan_schema_json_1 = __importDefault(require("../schemas/install-plan.schema.json"));
19
+ const package_smoke_schema_json_1 = __importDefault(require("../schemas/package-smoke.schema.json"));
20
+ const private_evidence_schema_json_1 = __importDefault(require("../schemas/private-evidence.schema.json"));
21
+ const provider_call_schema_json_1 = __importDefault(require("../schemas/provider-call.schema.json"));
22
+ const provider_config_schema_json_1 = __importDefault(require("../schemas/provider-config.schema.json"));
23
+ const release_artifact_manifest_schema_json_1 = __importDefault(require("../schemas/release-artifact-manifest.schema.json"));
24
+ const release_artifact_schema_json_1 = __importDefault(require("../schemas/release-artifact.schema.json"));
25
+ const release_dry_run_schema_json_1 = __importDefault(require("../schemas/release-dry-run.schema.json"));
26
+ const release_gate_schema_json_1 = __importDefault(require("../schemas/release-gate.schema.json"));
27
+ const release_publish_schema_json_1 = __importDefault(require("../schemas/release-publish.schema.json"));
28
+ const smoke_evidence_summary_schema_json_1 = __importDefault(require("../schemas/smoke-evidence-summary.schema.json"));
29
+ const tools_list_schema_json_1 = __importDefault(require("../schemas/tools-list.schema.json"));
30
+ const write_preflight_schema_json_1 = __importDefault(require("../schemas/write-preflight.schema.json"));
31
+ class SchemaValidationError extends Error {
32
+ schemaId;
33
+ issues;
34
+ constructor(schemaId, issues) {
35
+ const firstIssue = issues[0];
36
+ super(`Schema validation failed for ${schemaId}: ${firstIssue.path} ${firstIssue.message}`);
37
+ this.schemaId = schemaId;
38
+ this.issues = issues;
39
+ this.name = 'SchemaValidationError';
40
+ }
41
+ }
42
+ exports.SchemaValidationError = SchemaValidationError;
43
+ const schemaIndex = schema_index_json_1.default;
44
+ const registeredSchemas = {
45
+ 'hadara.active_run.projection.v1': active_run_projection_schema_json_1.default,
46
+ 'hadara.active_run.resume.v1': active_run_resume_schema_json_1.default,
47
+ 'hadara.cleanCheckoutSmoke.v1': clean_checkout_smoke_schema_json_1.default,
48
+ 'hadara.context.export.v1': context_export_schema_json_1.default,
49
+ 'hadara.evidence.list.v1': evidence_list_schema_json_1.default,
50
+ 'hadara.event.v1': event_schema_json_1.default,
51
+ 'hadara.featureSmoke.v1': feature_smoke_schema_json_1.default,
52
+ 'hadara.install.plan.v1': install_plan_schema_json_1.default,
53
+ 'hadara.packageSmoke.v1': package_smoke_schema_json_1.default,
54
+ 'hadara.privateEvidence.v1': private_evidence_schema_json_1.default,
55
+ 'hadara.provider.call.v1': provider_call_schema_json_1.default,
56
+ 'hadara.provider.config.v1': provider_config_schema_json_1.default,
57
+ 'hadara.releaseArtifact.manifest.v1': release_artifact_manifest_schema_json_1.default,
58
+ 'hadara.releaseArtifact.v1': release_artifact_schema_json_1.default,
59
+ 'hadara.releaseDryRun.v1': release_dry_run_schema_json_1.default,
60
+ 'hadara.releaseGate.v1': release_gate_schema_json_1.default,
61
+ 'hadara.releasePublish.v1': release_publish_schema_json_1.default,
62
+ 'hadara.smokeEvidenceSummary.v1': smoke_evidence_summary_schema_json_1.default,
63
+ 'hadara.tools.list.v1': tools_list_schema_json_1.default,
64
+ 'hadara.write.preflight.v1': write_preflight_schema_json_1.default
65
+ };
66
+ function loadSchema(schemaId) {
67
+ const indexEntry = schemaIndex.schemas.find((entry) => entry.id === schemaId);
68
+ const schema = registeredSchemas[schemaId];
69
+ if (!indexEntry || !schema) {
70
+ throw new Error(`Unknown HADARA schema: ${schemaId}`);
71
+ }
72
+ return schema;
73
+ }
74
+ function validateSchema(schemaId, value) {
75
+ const schema = loadSchema(schemaId);
76
+ const issues = [];
77
+ validateValue(value, schema, schema, '$', issues);
78
+ return {
79
+ ok: issues.length === 0,
80
+ schemaId,
81
+ issues
82
+ };
83
+ }
84
+ function assertSchema(schemaId, value) {
85
+ const result = validateSchema(schemaId, value);
86
+ if (!result.ok) {
87
+ throw new SchemaValidationError(schemaId, result.issues);
88
+ }
89
+ }
90
+ function validateValue(value, schema, rootSchema, valuePath, issues) {
91
+ const ref = stringProperty(schema, '$ref');
92
+ if (ref) {
93
+ validateValue(value, resolveLocalRef(rootSchema, ref), rootSchema, valuePath, issues);
94
+ return;
95
+ }
96
+ const oneOf = arrayProperty(schema, 'oneOf');
97
+ if (oneOf) {
98
+ const matchCount = oneOf.filter((candidate) => validateCandidate(value, candidate, rootSchema)).length;
99
+ if (matchCount !== 1) {
100
+ issues.push({
101
+ path: valuePath,
102
+ code: 'SCHEMA_ONE_OF_MISMATCH',
103
+ message: `must match exactly one schema option, matched ${matchCount}`
104
+ });
105
+ }
106
+ return;
107
+ }
108
+ const constValue = schema.const;
109
+ if (Object.prototype.hasOwnProperty.call(schema, 'const') && value !== constValue) {
110
+ issues.push({
111
+ path: valuePath,
112
+ code: 'SCHEMA_CONST_MISMATCH',
113
+ message: `must equal ${JSON.stringify(constValue)}`
114
+ });
115
+ }
116
+ const enumValues = arrayProperty(schema, 'enum');
117
+ if (enumValues && !enumValues.includes(value)) {
118
+ issues.push({
119
+ path: valuePath,
120
+ code: 'SCHEMA_ENUM_MISMATCH',
121
+ message: `must be one of ${enumValues.map((item) => JSON.stringify(item)).join(', ')}`
122
+ });
123
+ }
124
+ const schemaType = schema.type;
125
+ if (schemaType && !matchesType(value, schemaType)) {
126
+ issues.push({
127
+ path: valuePath,
128
+ code: 'SCHEMA_TYPE_MISMATCH',
129
+ message: `must be ${formatType(schemaType)}`
130
+ });
131
+ return;
132
+ }
133
+ if (typeof value === 'string') {
134
+ const minLength = numberProperty(schema, 'minLength');
135
+ if (minLength !== undefined && value.length < minLength) {
136
+ issues.push({
137
+ path: valuePath,
138
+ code: 'SCHEMA_MIN_LENGTH',
139
+ message: `must be at least ${minLength} character(s)`
140
+ });
141
+ }
142
+ const pattern = stringProperty(schema, 'pattern');
143
+ if (pattern && !new RegExp(pattern).test(value)) {
144
+ issues.push({
145
+ path: valuePath,
146
+ code: 'SCHEMA_PATTERN_MISMATCH',
147
+ message: `must match pattern ${pattern}`
148
+ });
149
+ }
150
+ }
151
+ if (isPlainObject(value)) {
152
+ validateObject(value, schema, rootSchema, valuePath, issues);
153
+ }
154
+ if (Array.isArray(value)) {
155
+ validateArray(value, schema, rootSchema, valuePath, issues);
156
+ }
157
+ }
158
+ function validateObject(value, schema, rootSchema, valuePath, issues) {
159
+ const required = arrayProperty(schema, 'required')?.filter((item) => typeof item === 'string') ?? [];
160
+ for (const key of required) {
161
+ if (!Object.prototype.hasOwnProperty.call(value, key)) {
162
+ issues.push({
163
+ path: `${valuePath}.${key}`,
164
+ code: 'SCHEMA_REQUIRED_MISSING',
165
+ message: 'is required'
166
+ });
167
+ }
168
+ }
169
+ const properties = objectProperty(schema, 'properties');
170
+ if (!properties)
171
+ return;
172
+ for (const [key, propertySchema] of Object.entries(properties)) {
173
+ if (!Object.prototype.hasOwnProperty.call(value, key))
174
+ continue;
175
+ if (!isPlainObject(propertySchema))
176
+ continue;
177
+ validateValue(value[key], propertySchema, rootSchema, `${valuePath}.${key}`, issues);
178
+ }
179
+ }
180
+ function validateArray(value, schema, rootSchema, valuePath, issues) {
181
+ const items = objectProperty(schema, 'items');
182
+ if (!items)
183
+ return;
184
+ value.forEach((item, index) => validateValue(item, items, rootSchema, `${valuePath}[${index}]`, issues));
185
+ }
186
+ function validateCandidate(value, schema, rootSchema) {
187
+ if (!isPlainObject(schema))
188
+ return false;
189
+ const issues = [];
190
+ validateValue(value, schema, rootSchema, '$', issues);
191
+ return issues.length === 0;
192
+ }
193
+ function resolveLocalRef(rootSchema, ref) {
194
+ if (!ref.startsWith('#/')) {
195
+ throw new Error(`Unsupported schema ref: ${ref}`);
196
+ }
197
+ let current = rootSchema;
198
+ for (const part of ref.slice(2).split('/')) {
199
+ if (!isPlainObject(current)) {
200
+ throw new Error(`Invalid schema ref: ${ref}`);
201
+ }
202
+ current = current[part];
203
+ }
204
+ if (!isPlainObject(current)) {
205
+ throw new Error(`Invalid schema ref: ${ref}`);
206
+ }
207
+ return current;
208
+ }
209
+ function matchesType(value, schemaType) {
210
+ if (Array.isArray(schemaType)) {
211
+ return schemaType.some((item) => matchesType(value, item));
212
+ }
213
+ switch (schemaType) {
214
+ case 'array':
215
+ return Array.isArray(value);
216
+ case 'boolean':
217
+ return typeof value === 'boolean';
218
+ case 'integer':
219
+ return Number.isInteger(value);
220
+ case 'null':
221
+ return value === null;
222
+ case 'number':
223
+ return typeof value === 'number' && Number.isFinite(value);
224
+ case 'object':
225
+ return isPlainObject(value);
226
+ case 'string':
227
+ return typeof value === 'string';
228
+ default:
229
+ return true;
230
+ }
231
+ }
232
+ function formatType(schemaType) {
233
+ return Array.isArray(schemaType) ? schemaType.map(String).join(' or ') : String(schemaType);
234
+ }
235
+ function isPlainObject(value) {
236
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
237
+ }
238
+ function objectProperty(value, key) {
239
+ const property = value[key];
240
+ return isPlainObject(property) ? property : undefined;
241
+ }
242
+ function arrayProperty(value, key) {
243
+ const property = value[key];
244
+ return Array.isArray(property) ? property : undefined;
245
+ }
246
+ function stringProperty(value, key) {
247
+ const property = value[key];
248
+ return typeof property === 'string' ? property : undefined;
249
+ }
250
+ function numberProperty(value, key) {
251
+ const property = value[key];
252
+ return typeof property === 'number' ? property : undefined;
253
+ }
@@ -0,0 +1,47 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.WorkspaceFileError = void 0;
7
+ exports.resolveProjectFile = resolveProjectFile;
8
+ exports.assertInsideProject = assertInsideProject;
9
+ exports.toProjectRelativePath = toProjectRelativePath;
10
+ const node_fs_1 = __importDefault(require("node:fs"));
11
+ const node_path_1 = __importDefault(require("node:path"));
12
+ const paths_1 = require("./paths");
13
+ class WorkspaceFileError extends Error {
14
+ code;
15
+ inputPath;
16
+ constructor(code, message, inputPath) {
17
+ super(message);
18
+ this.code = code;
19
+ this.inputPath = inputPath;
20
+ this.name = 'WorkspaceFileError';
21
+ }
22
+ }
23
+ exports.WorkspaceFileError = WorkspaceFileError;
24
+ function resolveProjectFile(projectRoot, inputPath) {
25
+ const absolutePath = node_path_1.default.resolve(projectRoot, inputPath);
26
+ assertInsideProject(projectRoot, absolutePath, inputPath);
27
+ if (!node_fs_1.default.existsSync(absolutePath)) {
28
+ throw new WorkspaceFileError('WORKSPACE_FILE_NOT_FOUND', 'Workspace file input was not found.', inputPath);
29
+ }
30
+ if (!node_fs_1.default.statSync(absolutePath).isFile()) {
31
+ throw new WorkspaceFileError('WORKSPACE_FILE_NOT_FILE', 'Workspace file input must be a file.', inputPath);
32
+ }
33
+ assertInsideProject(projectRoot, node_fs_1.default.realpathSync.native(absolutePath), inputPath);
34
+ return {
35
+ absolutePath,
36
+ relativePath: toProjectRelativePath(projectRoot, absolutePath)
37
+ };
38
+ }
39
+ function assertInsideProject(projectRoot, candidatePath, inputPath = candidatePath) {
40
+ if (!(0, paths_1.isInside)(projectRoot, candidatePath)) {
41
+ throw new WorkspaceFileError('WORKSPACE_FILE_OUTSIDE', 'Workspace file input must be inside the project root.', inputPath);
42
+ }
43
+ }
44
+ function toProjectRelativePath(projectRoot, absolutePath) {
45
+ assertInsideProject(projectRoot, absolutePath);
46
+ return node_path_1.default.relative(projectRoot, absolutePath).split(node_path_1.default.sep).join('/');
47
+ }