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.
- package/LICENSE +21 -0
- package/README.md +109 -0
- package/dist/agent/evidence.js +50 -0
- package/dist/agent/loop.js +124 -0
- package/dist/cli/args.js +70 -0
- package/dist/cli/dashboard.js +185 -0
- package/dist/cli/debt.js +41 -0
- package/dist/cli/doctor.js +68 -0
- package/dist/cli/errors.js +58 -0
- package/dist/cli/evidence-json.js +75 -0
- package/dist/cli/evidence.js +80 -0
- package/dist/cli/handoff.js +16 -0
- package/dist/cli/harness.js +57 -0
- package/dist/cli/hermes-json.js +31 -0
- package/dist/cli/hermes.js +28 -0
- package/dist/cli/init.js +142 -0
- package/dist/cli/install.js +34 -0
- package/dist/cli/main.js +216 -0
- package/dist/cli/mcp.js +15 -0
- package/dist/cli/package-smoke.js +37 -0
- package/dist/cli/policy-json.js +22 -0
- package/dist/cli/policy.js +43 -0
- package/dist/cli/release-artifact.js +47 -0
- package/dist/cli/release-dry-run.js +24 -0
- package/dist/cli/release-gate.js +28 -0
- package/dist/cli/release-publish.js +41 -0
- package/dist/cli/run-scaffold.js +68 -0
- package/dist/cli/run-state.js +41 -0
- package/dist/cli/run.js +191 -0
- package/dist/cli/smoke.js +58 -0
- package/dist/cli/status-json.js +6 -0
- package/dist/cli/status.js +26 -0
- package/dist/cli/task-json.js +8 -0
- package/dist/cli/task.js +64 -0
- package/dist/cli/tools.js +25 -0
- package/dist/cli/tui.js +72 -0
- package/dist/cli/write-preflight.js +27 -0
- package/dist/core/audit.js +41 -0
- package/dist/core/events.js +63 -0
- package/dist/core/fs.js +44 -0
- package/dist/core/paths.js +59 -0
- package/dist/core/redaction.js +178 -0
- package/dist/core/schema.js +253 -0
- package/dist/core/workspace.js +47 -0
- package/dist/evidence/evidence.js +170 -0
- package/dist/evidence/private-manifest.js +101 -0
- package/dist/handoff/handoff.js +49 -0
- package/dist/harness/replay.js +200 -0
- package/dist/harness/validate.js +465 -0
- package/dist/hermes/context-export.js +104 -0
- package/dist/index.js +29 -0
- package/dist/mcp/server.js +104 -0
- package/dist/mcp/tool-dispatch.js +159 -0
- package/dist/mcp/tool-registry.js +150 -0
- package/dist/mcp/tool-schemas.js +18 -0
- package/dist/policy/command-risk.js +39 -0
- package/dist/policy/permission-matrix.js +42 -0
- package/dist/policy/policy.js +20 -0
- package/dist/policy/preflight.js +47 -0
- package/dist/policy/presets.js +24 -0
- package/dist/policy/tokenizer.js +53 -0
- package/dist/providers/fallback-executor.js +46 -0
- package/dist/providers/mock-provider.js +49 -0
- package/dist/providers/provider-contract.js +2 -0
- package/dist/providers/provider-preparation.js +220 -0
- package/dist/providers/scripted-provider.js +69 -0
- package/dist/schemas/active-run-projection.schema.json +73 -0
- package/dist/schemas/active-run-resume.schema.json +68 -0
- package/dist/schemas/clean-checkout-smoke.schema.json +126 -0
- package/dist/schemas/context-export.schema.json +35 -0
- package/dist/schemas/event.schema.json +17 -0
- package/dist/schemas/evidence-list.schema.json +49 -0
- package/dist/schemas/feature-smoke.schema.json +67 -0
- package/dist/schemas/install-plan.schema.json +93 -0
- package/dist/schemas/package-smoke.schema.json +130 -0
- package/dist/schemas/private-evidence.schema.json +48 -0
- package/dist/schemas/provider-call.schema.json +42 -0
- package/dist/schemas/provider-config.schema.json +43 -0
- package/dist/schemas/release-artifact-manifest.schema.json +55 -0
- package/dist/schemas/release-artifact.schema.json +140 -0
- package/dist/schemas/release-dry-run.schema.json +141 -0
- package/dist/schemas/release-gate.schema.json +42 -0
- package/dist/schemas/release-publish.schema.json +114 -0
- package/dist/schemas/schema-index.json +145 -0
- package/dist/schemas/smoke-evidence-summary.schema.json +88 -0
- package/dist/schemas/tools-list.schema.json +78 -0
- package/dist/schemas/write-preflight.schema.json +47 -0
- package/dist/services/active-run-state.js +215 -0
- package/dist/services/capability-registry.js +540 -0
- package/dist/services/clean-checkout-smoke.js +393 -0
- package/dist/services/evidence-list.js +136 -0
- package/dist/services/feature-smoke.js +155 -0
- package/dist/services/harness-service.js +7 -0
- package/dist/services/install-plan.js +233 -0
- package/dist/services/operational-debt.js +767 -0
- package/dist/services/operations-status-service.js +195 -0
- package/dist/services/package-smoke.js +676 -0
- package/dist/services/policy-service.js +25 -0
- package/dist/services/project-read-model.js +101 -0
- package/dist/services/release-artifact-evidence.js +77 -0
- package/dist/services/release-artifact.js +351 -0
- package/dist/services/release-dry-run.js +253 -0
- package/dist/services/release-evidence.js +138 -0
- package/dist/services/release-publish.js +163 -0
- package/dist/services/smoke-evidence.js +104 -0
- package/dist/services/task-read-model.js +125 -0
- package/dist/services/tools-list.js +26 -0
- package/dist/services/write-preflight.js +240 -0
- package/dist/task/task-capsule.js +121 -0
- package/dist/tools/fake-shell.js +56 -0
- package/dist/tui/cache.js +341 -0
- package/dist/tui/constants.js +44 -0
- package/dist/tui/layout.js +140 -0
- package/dist/tui/markdown.js +238 -0
- package/dist/tui/read-model-worker.js +24 -0
- package/dist/tui/read-model.js +502 -0
- package/dist/tui/snapshot.js +434 -0
- package/dist/tui/state.js +229 -0
- package/dist/tui/terminal.js +475 -0
- package/dist/tui/theme.js +86 -0
- package/package.json +16 -0
|
@@ -0,0 +1,393 @@
|
|
|
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.createCleanCheckoutSmokeReport = createCleanCheckoutSmokeReport;
|
|
7
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
8
|
+
const node_os_1 = __importDefault(require("node:os"));
|
|
9
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
10
|
+
const node_child_process_1 = require("node:child_process");
|
|
11
|
+
const schema_1 = require("../core/schema");
|
|
12
|
+
const smoke_evidence_1 = require("./smoke-evidence");
|
|
13
|
+
function createCleanCheckoutSmokeReport(options) {
|
|
14
|
+
const issues = [];
|
|
15
|
+
validateTimeout(options.timeoutSeconds, issues);
|
|
16
|
+
if (options.execute !== true) {
|
|
17
|
+
issues.push({
|
|
18
|
+
severity: 'error',
|
|
19
|
+
code: 'CLEAN_CHECKOUT_SMOKE_EXECUTION_REQUIRED',
|
|
20
|
+
message: 'Clean-checkout smoke requires explicit --execute because it runs npm commands in a disposable copy.'
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
const workspaceSetup = prepareWorkspace(options.paths.projectRoot, options.workspace, options.keepTemp === true, issues);
|
|
24
|
+
const execution = {
|
|
25
|
+
sourceCopied: false,
|
|
26
|
+
dependencyInstallExecuted: false,
|
|
27
|
+
buildExecuted: false,
|
|
28
|
+
checkExecuted: false,
|
|
29
|
+
builtCliSmokeExecuted: false,
|
|
30
|
+
packageInstallExecuted: false,
|
|
31
|
+
releaseMutationExecuted: false,
|
|
32
|
+
publishExecuted: false
|
|
33
|
+
};
|
|
34
|
+
const steps = [];
|
|
35
|
+
const runner = options.runner ?? runCommand;
|
|
36
|
+
const timeoutMs = (options.timeoutSeconds ?? 180) * 1000;
|
|
37
|
+
validateTaskId(options.taskId, issues);
|
|
38
|
+
try {
|
|
39
|
+
if (issues.some((issue) => issue.severity === 'error') || !workspaceSetup.ok) {
|
|
40
|
+
pushSkippedSteps(steps);
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
try {
|
|
44
|
+
copySourceCheckout(options.paths.projectRoot, workspaceSetup.path);
|
|
45
|
+
execution.sourceCopied = true;
|
|
46
|
+
steps.push({
|
|
47
|
+
id: 'copy-source',
|
|
48
|
+
label: 'Copy source checkout',
|
|
49
|
+
command: 'copy source checkout to <redacted-disposable-workspace>',
|
|
50
|
+
status: 'passed',
|
|
51
|
+
summary: 'Source checkout was copied into a disposable workspace without including dependency, build, local, or private store directories.'
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
issues.push({
|
|
56
|
+
severity: 'error',
|
|
57
|
+
code: 'CLEAN_CHECKOUT_SOURCE_COPY_FAILED',
|
|
58
|
+
message: 'Source checkout could not be copied into the disposable workspace.',
|
|
59
|
+
stepId: 'copy-source'
|
|
60
|
+
});
|
|
61
|
+
steps.push({
|
|
62
|
+
id: 'copy-source',
|
|
63
|
+
label: 'Copy source checkout',
|
|
64
|
+
command: 'copy source checkout to <redacted-disposable-workspace>',
|
|
65
|
+
status: 'failed',
|
|
66
|
+
summary: 'Source checkout copy failed.'
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
if (execution.sourceCopied) {
|
|
70
|
+
const plannedSteps = [
|
|
71
|
+
{
|
|
72
|
+
id: 'npm-ci',
|
|
73
|
+
label: 'Clean dependency install',
|
|
74
|
+
command: 'npm ci',
|
|
75
|
+
args: ['ci'],
|
|
76
|
+
mark: () => {
|
|
77
|
+
execution.dependencyInstallExecuted = true;
|
|
78
|
+
}
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
id: 'build',
|
|
82
|
+
label: 'Build clean checkout',
|
|
83
|
+
command: 'npm run build',
|
|
84
|
+
args: ['run', 'build'],
|
|
85
|
+
mark: () => {
|
|
86
|
+
execution.buildExecuted = true;
|
|
87
|
+
}
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
id: 'check',
|
|
91
|
+
label: 'Check clean checkout',
|
|
92
|
+
command: 'npm run check',
|
|
93
|
+
args: ['run', 'check'],
|
|
94
|
+
mark: () => {
|
|
95
|
+
execution.checkExecuted = true;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
];
|
|
99
|
+
for (const step of plannedSteps) {
|
|
100
|
+
if (issues.some((issue) => issue.severity === 'error')) {
|
|
101
|
+
steps.push(skippedStep(step.id, step.label, step.command, 'Skipped because a previous clean-checkout step failed.'));
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
step.mark();
|
|
105
|
+
const result = runner(npmCommand(), step.args, { cwd: workspaceSetup.path, timeoutMs });
|
|
106
|
+
steps.push(commandStep(step.id, step.label, step.command, result, issues));
|
|
107
|
+
}
|
|
108
|
+
for (const step of builtCliSteps()) {
|
|
109
|
+
if (issues.some((issue) => issue.severity === 'error')) {
|
|
110
|
+
steps.push(skippedStep(step.id, step.label, step.command, 'Skipped because a previous clean-checkout step failed.'));
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
execution.builtCliSmokeExecuted = true;
|
|
114
|
+
const result = runner(nodeCommand(), step.args, { cwd: workspaceSetup.path, timeoutMs });
|
|
115
|
+
const smokeStep = commandStep(step.id, step.label, step.command, result, issues);
|
|
116
|
+
if (result.status === 0 && !isOkJsonReport(result.stdout)) {
|
|
117
|
+
smokeStep.status = 'failed';
|
|
118
|
+
smokeStep.summary = `${step.label} did not return an ok reduced JSON report.`;
|
|
119
|
+
issues.push({
|
|
120
|
+
severity: 'error',
|
|
121
|
+
code: 'CLEAN_CHECKOUT_BUILT_CLI_JSON_NOT_OK',
|
|
122
|
+
message: `${step.command} did not return an ok reduced JSON report.`,
|
|
123
|
+
stepId: step.id
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
steps.push(smokeStep);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
else {
|
|
130
|
+
pushSkippedExecutionAfterCopy(steps);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
finally {
|
|
135
|
+
const cleanup = cleanupWorkspace(workspaceSetup, options.keepTemp === true);
|
|
136
|
+
steps.push({
|
|
137
|
+
id: 'cleanup',
|
|
138
|
+
label: 'Cleanup disposable workspace',
|
|
139
|
+
command: 'remove <redacted-disposable-workspace>',
|
|
140
|
+
status: cleanup.ok ? 'passed' : 'failed',
|
|
141
|
+
summary: cleanup.summary
|
|
142
|
+
});
|
|
143
|
+
if (!cleanup.ok) {
|
|
144
|
+
issues.push({
|
|
145
|
+
severity: 'warning',
|
|
146
|
+
code: 'CLEAN_CHECKOUT_CLEANUP_FAILED',
|
|
147
|
+
message: 'Clean-checkout smoke cleanup could not remove the disposable workspace.',
|
|
148
|
+
stepId: 'cleanup'
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
const report = {
|
|
153
|
+
schemaVersion: 'hadara.cleanCheckoutSmoke.v1',
|
|
154
|
+
command: 'smoke.cleanCheckout',
|
|
155
|
+
ok: issues.every((issue) => issue.severity !== 'error') && steps.every((step) => step.status !== 'failed'),
|
|
156
|
+
mode: 'execute',
|
|
157
|
+
execution,
|
|
158
|
+
workspace: {
|
|
159
|
+
kind: 'disposable-clean-checkout',
|
|
160
|
+
displayPath: workspaceSetup.displayPath,
|
|
161
|
+
pathRedacted: true,
|
|
162
|
+
...(workspaceSetup.relativePath ? { relativePath: workspaceSetup.relativePath } : {}),
|
|
163
|
+
retention: options.keepTemp === true ? 'kept-temporary' : 'deleted'
|
|
164
|
+
},
|
|
165
|
+
source: {
|
|
166
|
+
kind: 'source-checkout',
|
|
167
|
+
displayPath: '.',
|
|
168
|
+
pathRedacted: true,
|
|
169
|
+
relativePath: '.',
|
|
170
|
+
mutated: false
|
|
171
|
+
},
|
|
172
|
+
steps,
|
|
173
|
+
artifacts: [],
|
|
174
|
+
privacy: {
|
|
175
|
+
rawLogsIncluded: false,
|
|
176
|
+
privatePathsIncluded: false,
|
|
177
|
+
environmentSecretsIncluded: false,
|
|
178
|
+
privateStorePathsIncluded: false
|
|
179
|
+
},
|
|
180
|
+
issues
|
|
181
|
+
};
|
|
182
|
+
if (options.attachEvidence === true && options.taskId && options.noEvidence !== true) {
|
|
183
|
+
const evidence = (0, smoke_evidence_1.attachReducedSmokeEvidence)({
|
|
184
|
+
projectRoot: options.paths.projectRoot,
|
|
185
|
+
taskId: options.taskId,
|
|
186
|
+
category: 'clean-checkout-smoke',
|
|
187
|
+
kind: 'command-log',
|
|
188
|
+
summary: `Clean-checkout smoke ${report.ok ? 'passed' : 'failed'} with reduced public evidence.`,
|
|
189
|
+
result: report.ok ? 'passed' : 'failed',
|
|
190
|
+
report
|
|
191
|
+
});
|
|
192
|
+
report.artifacts.push(evidence.artifact);
|
|
193
|
+
report.steps.push({
|
|
194
|
+
id: 'evidence',
|
|
195
|
+
label: 'Attach reduced public evidence',
|
|
196
|
+
command: 'write reduced public smoke evidence summary',
|
|
197
|
+
status: 'passed',
|
|
198
|
+
summary: 'Reduced clean-checkout smoke summary was attached as public evidence after redaction checks.'
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
(0, schema_1.assertSchema)('hadara.cleanCheckoutSmoke.v1', report);
|
|
202
|
+
return report;
|
|
203
|
+
}
|
|
204
|
+
function builtCliSteps() {
|
|
205
|
+
return [
|
|
206
|
+
{
|
|
207
|
+
id: 'doctor',
|
|
208
|
+
label: 'Built CLI doctor',
|
|
209
|
+
command: 'node dist/cli/main.js doctor --json --project <redacted-clean-checkout>',
|
|
210
|
+
args: ['dist/cli/main.js', 'doctor', '--json', '--project', '.']
|
|
211
|
+
},
|
|
212
|
+
{
|
|
213
|
+
id: 'ops-status',
|
|
214
|
+
label: 'Built CLI operations status',
|
|
215
|
+
command: 'node dist/cli/main.js ops status --json --project <redacted-clean-checkout>',
|
|
216
|
+
args: ['dist/cli/main.js', 'ops', 'status', '--json', '--project', '.']
|
|
217
|
+
},
|
|
218
|
+
{
|
|
219
|
+
id: 'release-gate-strict',
|
|
220
|
+
label: 'Built CLI strict release gate',
|
|
221
|
+
command: 'node dist/cli/main.js release gate --mode strict --json --project <redacted-clean-checkout>',
|
|
222
|
+
args: ['dist/cli/main.js', 'release', 'gate', '--mode', 'strict', '--json', '--project', '.']
|
|
223
|
+
}
|
|
224
|
+
];
|
|
225
|
+
}
|
|
226
|
+
function commandStep(id, label, command, result, issues) {
|
|
227
|
+
const status = result.status === 0 && !result.timedOut ? 'passed' : 'failed';
|
|
228
|
+
if (status === 'failed') {
|
|
229
|
+
issues.push({
|
|
230
|
+
severity: 'error',
|
|
231
|
+
code: result.timedOut ? 'CLEAN_CHECKOUT_STEP_TIMEOUT' : 'CLEAN_CHECKOUT_STEP_FAILED',
|
|
232
|
+
message: result.timedOut ? `${command} timed out during clean-checkout smoke.` : `${command} failed during clean-checkout smoke.`,
|
|
233
|
+
stepId: id
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
return {
|
|
237
|
+
id,
|
|
238
|
+
label,
|
|
239
|
+
command,
|
|
240
|
+
status,
|
|
241
|
+
exitCode: result.status,
|
|
242
|
+
elapsedMs: result.elapsedMs,
|
|
243
|
+
summary: status === 'passed' ? `${label} completed successfully.` : `${label} failed with a reduced exit summary.`
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
function skippedStep(id, label, command, summary) {
|
|
247
|
+
return { id, label, command, status: 'skipped', summary };
|
|
248
|
+
}
|
|
249
|
+
function pushSkippedSteps(steps) {
|
|
250
|
+
steps.push(skippedStep('copy-source', 'Copy source checkout', 'copy source checkout to <redacted-disposable-workspace>', 'Skipped because clean-checkout smoke setup failed.'));
|
|
251
|
+
pushSkippedExecutionAfterCopy(steps);
|
|
252
|
+
}
|
|
253
|
+
function pushSkippedExecutionAfterCopy(steps) {
|
|
254
|
+
steps.push(skippedStep('npm-ci', 'Clean dependency install', 'npm ci', 'Skipped because source copy failed.'), skippedStep('build', 'Build clean checkout', 'npm run build', 'Skipped because source copy failed.'), skippedStep('check', 'Check clean checkout', 'npm run check', 'Skipped because source copy failed.'), skippedStep('doctor', 'Built CLI doctor', 'node dist/cli/main.js doctor --json --project <redacted-clean-checkout>', 'Skipped because source copy failed.'), skippedStep('ops-status', 'Built CLI operations status', 'node dist/cli/main.js ops status --json --project <redacted-clean-checkout>', 'Skipped because source copy failed.'), skippedStep('release-gate-strict', 'Built CLI strict release gate', 'node dist/cli/main.js release gate --mode strict --json --project <redacted-clean-checkout>', 'Skipped because source copy failed.'));
|
|
255
|
+
}
|
|
256
|
+
function prepareWorkspace(projectRoot, workspace, keepTemp, issues) {
|
|
257
|
+
const resolved = workspace ? node_path_1.default.resolve(projectRoot, workspace) : node_fs_1.default.mkdtempSync(node_path_1.default.join(node_os_1.default.tmpdir(), 'hadara-clean-checkout-'));
|
|
258
|
+
const relativePath = safeRelativePath(workspace);
|
|
259
|
+
if (isInsideProject(projectRoot, resolved)) {
|
|
260
|
+
issues.push({
|
|
261
|
+
severity: 'error',
|
|
262
|
+
code: 'CLEAN_CHECKOUT_WORKSPACE_INSIDE_PROJECT',
|
|
263
|
+
message: 'Clean-checkout smoke workspace must be outside the source workspace.'
|
|
264
|
+
});
|
|
265
|
+
return {
|
|
266
|
+
ok: false,
|
|
267
|
+
path: resolved,
|
|
268
|
+
displayPath: relativePath ? `./${relativePath}` : '<redacted-clean-checkout-workspace>',
|
|
269
|
+
...(relativePath ? { relativePath } : {}),
|
|
270
|
+
cleanupAllowed: false
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
try {
|
|
274
|
+
node_fs_1.default.mkdirSync(resolved, { recursive: true });
|
|
275
|
+
return {
|
|
276
|
+
ok: true,
|
|
277
|
+
path: resolved,
|
|
278
|
+
displayPath: relativePath ? `./${relativePath}` : '<redacted-clean-checkout-workspace>',
|
|
279
|
+
...(relativePath ? { relativePath } : {}),
|
|
280
|
+
cleanupAllowed: !keepTemp
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
catch {
|
|
284
|
+
issues.push({
|
|
285
|
+
severity: 'error',
|
|
286
|
+
code: 'CLEAN_CHECKOUT_WORKSPACE_CREATE_FAILED',
|
|
287
|
+
message: 'Clean-checkout smoke workspace could not be created.'
|
|
288
|
+
});
|
|
289
|
+
return {
|
|
290
|
+
ok: false,
|
|
291
|
+
path: resolved,
|
|
292
|
+
displayPath: relativePath ? `./${relativePath}` : '<redacted-clean-checkout-workspace>',
|
|
293
|
+
...(relativePath ? { relativePath } : {}),
|
|
294
|
+
cleanupAllowed: false
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
function copySourceCheckout(sourceRoot, targetRoot) {
|
|
299
|
+
node_fs_1.default.cpSync(sourceRoot, targetRoot, {
|
|
300
|
+
recursive: true,
|
|
301
|
+
dereference: false,
|
|
302
|
+
filter: (source) => {
|
|
303
|
+
const relative = node_path_1.default.relative(sourceRoot, source).split(node_path_1.default.sep).join('/');
|
|
304
|
+
if (!relative)
|
|
305
|
+
return true;
|
|
306
|
+
return !(relative === 'node_modules' ||
|
|
307
|
+
relative.startsWith('node_modules/') ||
|
|
308
|
+
relative === 'dist' ||
|
|
309
|
+
relative.startsWith('dist/') ||
|
|
310
|
+
relative === '.hadara/local' ||
|
|
311
|
+
relative.startsWith('.hadara/local/') ||
|
|
312
|
+
relative === '.git' ||
|
|
313
|
+
relative.startsWith('.git/'));
|
|
314
|
+
}
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
function cleanupWorkspace(workspace, keepTemp) {
|
|
318
|
+
if (keepTemp)
|
|
319
|
+
return { ok: true, summary: 'Disposable clean checkout retained because --keep-temp was set; retained files are local/private only and should not be committed.' };
|
|
320
|
+
if (!workspace.cleanupAllowed)
|
|
321
|
+
return { ok: true, summary: 'No disposable workspace cleanup was required.' };
|
|
322
|
+
try {
|
|
323
|
+
node_fs_1.default.rmSync(workspace.path, { recursive: true, force: true });
|
|
324
|
+
return { ok: true, summary: 'Disposable clean checkout was removed.' };
|
|
325
|
+
}
|
|
326
|
+
catch {
|
|
327
|
+
return { ok: false, summary: 'Disposable clean checkout cleanup failed.' };
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
function runCommand(command, args, options) {
|
|
331
|
+
const started = Date.now();
|
|
332
|
+
const result = (0, node_child_process_1.spawnSync)(command, args, {
|
|
333
|
+
cwd: options.cwd,
|
|
334
|
+
env: options.env ?? process.env,
|
|
335
|
+
encoding: 'utf8',
|
|
336
|
+
timeout: options.timeoutMs,
|
|
337
|
+
maxBuffer: 1024 * 1024 * 8
|
|
338
|
+
});
|
|
339
|
+
return {
|
|
340
|
+
status: typeof result.status === 'number' ? result.status : null,
|
|
341
|
+
signal: result.signal,
|
|
342
|
+
stdout: result.stdout ?? '',
|
|
343
|
+
stderr: result.stderr ?? '',
|
|
344
|
+
elapsedMs: Date.now() - started,
|
|
345
|
+
timedOut: result.error?.name === 'TimeoutError' || result.signal === 'SIGTERM'
|
|
346
|
+
};
|
|
347
|
+
}
|
|
348
|
+
function isOkJsonReport(stdout) {
|
|
349
|
+
try {
|
|
350
|
+
const parsed = JSON.parse(stdout);
|
|
351
|
+
return parsed.ok === true;
|
|
352
|
+
}
|
|
353
|
+
catch {
|
|
354
|
+
return false;
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
function validateTimeout(timeoutSeconds, issues) {
|
|
358
|
+
if (timeoutSeconds !== undefined && (!Number.isSafeInteger(timeoutSeconds) || timeoutSeconds < 1)) {
|
|
359
|
+
issues.push({
|
|
360
|
+
severity: 'error',
|
|
361
|
+
code: 'CLEAN_CHECKOUT_TIMEOUT_INVALID',
|
|
362
|
+
message: 'Clean-checkout smoke timeout must be a positive integer number of seconds.'
|
|
363
|
+
});
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
function validateTaskId(taskId, issues) {
|
|
367
|
+
if (taskId !== undefined && !/^T-[0-9]{4}$/.test(taskId)) {
|
|
368
|
+
issues.push({
|
|
369
|
+
severity: 'error',
|
|
370
|
+
code: 'CLEAN_CHECKOUT_TASK_ID_INVALID',
|
|
371
|
+
message: 'Clean-checkout smoke task id must look like T-0000.'
|
|
372
|
+
});
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
function npmCommand() {
|
|
376
|
+
return process.platform === 'win32' ? 'npm.cmd' : 'npm';
|
|
377
|
+
}
|
|
378
|
+
function nodeCommand() {
|
|
379
|
+
return process.execPath;
|
|
380
|
+
}
|
|
381
|
+
function safeRelativePath(value) {
|
|
382
|
+
if (!value || node_path_1.default.isAbsolute(value) || /^[A-Za-z]:[\\/]/.test(value) || value.startsWith('~') || value.startsWith('<') || value.includes('%')) {
|
|
383
|
+
return undefined;
|
|
384
|
+
}
|
|
385
|
+
const normalized = value.split(/[\\/]+/).filter(Boolean).join('/');
|
|
386
|
+
if (!normalized || normalized === '.' || normalized.startsWith('..'))
|
|
387
|
+
return undefined;
|
|
388
|
+
return normalized;
|
|
389
|
+
}
|
|
390
|
+
function isInsideProject(projectRoot, child) {
|
|
391
|
+
const relative = node_path_1.default.relative(node_path_1.default.resolve(projectRoot), node_path_1.default.resolve(child));
|
|
392
|
+
return relative === '' || (!relative.startsWith('..') && !node_path_1.default.isAbsolute(relative));
|
|
393
|
+
}
|
|
@@ -0,0 +1,136 @@
|
|
|
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.createEvidenceListReport = createEvidenceListReport;
|
|
7
|
+
exports.parseEvidenceIndexFile = parseEvidenceIndexFile;
|
|
8
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
9
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
10
|
+
const redaction_1 = require("../core/redaction");
|
|
11
|
+
const task_capsule_1 = require("../task/task-capsule");
|
|
12
|
+
const DEFAULT_LIMIT = 50;
|
|
13
|
+
function createEvidenceListReport(projectRoot, input) {
|
|
14
|
+
const task = (0, task_capsule_1.listTaskCapsules)(projectRoot).find((item) => item.id === input.taskId);
|
|
15
|
+
if (!task) {
|
|
16
|
+
return {
|
|
17
|
+
schemaVersion: 'hadara.evidence.list.v1',
|
|
18
|
+
command: 'evidence.list',
|
|
19
|
+
ok: false,
|
|
20
|
+
taskId: input.taskId,
|
|
21
|
+
count: 0,
|
|
22
|
+
records: [],
|
|
23
|
+
issues: [
|
|
24
|
+
{
|
|
25
|
+
severity: 'error',
|
|
26
|
+
code: 'TASK_NOT_FOUND',
|
|
27
|
+
message: `Task Capsule not found: ${input.taskId}`
|
|
28
|
+
}
|
|
29
|
+
]
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
const parsed = parseEvidenceIndexFile(node_path_1.default.join(task.dir, 'evidence.jsonl'), input.taskId);
|
|
33
|
+
const limit = normalizeLimit(input.limit);
|
|
34
|
+
const includePrivate = input.includePrivate === true;
|
|
35
|
+
const records = parsed.records.filter((record) => includePrivate || record.visibility !== 'private').slice(0, limit);
|
|
36
|
+
return {
|
|
37
|
+
schemaVersion: 'hadara.evidence.list.v1',
|
|
38
|
+
command: 'evidence.list',
|
|
39
|
+
ok: !parsed.issues.some((issue) => issue.severity === 'error'),
|
|
40
|
+
taskId: input.taskId,
|
|
41
|
+
count: records.length,
|
|
42
|
+
records,
|
|
43
|
+
issues: parsed.issues
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
function parseEvidenceIndexFile(indexPath, taskId) {
|
|
47
|
+
if (!node_fs_1.default.existsSync(indexPath)) {
|
|
48
|
+
return {
|
|
49
|
+
records: [],
|
|
50
|
+
issues: [
|
|
51
|
+
{
|
|
52
|
+
severity: 'warning',
|
|
53
|
+
code: 'EVIDENCE_INDEX_MISSING',
|
|
54
|
+
message: 'evidence.jsonl is missing.'
|
|
55
|
+
}
|
|
56
|
+
]
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
const content = node_fs_1.default.readFileSync(indexPath, 'utf8').trim();
|
|
60
|
+
if (!content)
|
|
61
|
+
return { records: [], issues: [] };
|
|
62
|
+
const records = [];
|
|
63
|
+
const issues = [];
|
|
64
|
+
content.split(/\r?\n/).forEach((line, index) => {
|
|
65
|
+
try {
|
|
66
|
+
const record = JSON.parse(line);
|
|
67
|
+
if (isEvidenceIndexRecord(record)) {
|
|
68
|
+
if (record.taskId !== taskId) {
|
|
69
|
+
issues.push({
|
|
70
|
+
severity: 'warning',
|
|
71
|
+
code: 'EVIDENCE_RECORD_TASK_MISMATCH',
|
|
72
|
+
message: `evidence.jsonl line ${index + 1} has taskId ${record.taskId}, expected ${taskId}.`
|
|
73
|
+
});
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
records.push(normalizeEvidenceIndexRecord(record));
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
issues.push({
|
|
80
|
+
severity: 'warning',
|
|
81
|
+
code: 'EVIDENCE_RECORD_INVALID',
|
|
82
|
+
message: `evidence.jsonl line ${index + 1} is not a supported evidence record.`
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
issues.push({
|
|
87
|
+
severity: 'warning',
|
|
88
|
+
code: 'EVIDENCE_INDEX_JSON_INVALID',
|
|
89
|
+
message: `evidence.jsonl line ${index + 1} is not valid JSON.`
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
return { records, issues };
|
|
94
|
+
}
|
|
95
|
+
function isEvidenceIndexRecord(value) {
|
|
96
|
+
if (typeof value !== 'object' || value === null)
|
|
97
|
+
return false;
|
|
98
|
+
const record = value;
|
|
99
|
+
return (record.schemaVersion === 'hadara.evidence.v1' &&
|
|
100
|
+
typeof record.time === 'string' &&
|
|
101
|
+
typeof record.taskId === 'string' &&
|
|
102
|
+
isEvidenceKind(record.kind) &&
|
|
103
|
+
typeof record.summary === 'string' &&
|
|
104
|
+
isEvidenceResult(record.result) &&
|
|
105
|
+
isEvidenceVisibility(record.visibility) &&
|
|
106
|
+
(record.evidencePath === undefined || typeof record.evidencePath === 'string'));
|
|
107
|
+
}
|
|
108
|
+
function normalizeEvidenceIndexRecord(record) {
|
|
109
|
+
const normalized = {
|
|
110
|
+
schemaVersion: 'hadara.evidence.v1',
|
|
111
|
+
time: record.time,
|
|
112
|
+
taskId: record.taskId,
|
|
113
|
+
kind: record.kind,
|
|
114
|
+
summary: (0, redaction_1.redactSecrets)(record.summary),
|
|
115
|
+
result: record.result,
|
|
116
|
+
visibility: record.visibility
|
|
117
|
+
};
|
|
118
|
+
if (record.visibility === 'public' && record.evidencePath) {
|
|
119
|
+
normalized.evidencePath = record.evidencePath;
|
|
120
|
+
}
|
|
121
|
+
return normalized;
|
|
122
|
+
}
|
|
123
|
+
function isEvidenceKind(value) {
|
|
124
|
+
return value === 'test-log' || value === 'command-log' || value === 'diff-summary' || value === 'screenshot' || value === 'note';
|
|
125
|
+
}
|
|
126
|
+
function isEvidenceResult(value) {
|
|
127
|
+
return value === 'passed' || value === 'failed' || value === 'blocked' || value === 'unknown';
|
|
128
|
+
}
|
|
129
|
+
function isEvidenceVisibility(value) {
|
|
130
|
+
return value === 'public' || value === 'private';
|
|
131
|
+
}
|
|
132
|
+
function normalizeLimit(value) {
|
|
133
|
+
if (value === undefined)
|
|
134
|
+
return DEFAULT_LIMIT;
|
|
135
|
+
return Math.max(0, Math.floor(value));
|
|
136
|
+
}
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createFeatureSmokeReport = createFeatureSmokeReport;
|
|
4
|
+
const schema_1 = require("../core/schema");
|
|
5
|
+
const doctor_1 = require("../cli/doctor");
|
|
6
|
+
const operational_debt_1 = require("./operational-debt");
|
|
7
|
+
const operations_status_service_1 = require("./operations-status-service");
|
|
8
|
+
const task_read_model_1 = require("./task-read-model");
|
|
9
|
+
const tools_list_1 = require("./tools-list");
|
|
10
|
+
const read_model_1 = require("../tui/read-model");
|
|
11
|
+
const snapshot_1 = require("../tui/snapshot");
|
|
12
|
+
function createFeatureSmokeReport(options) {
|
|
13
|
+
const issues = [];
|
|
14
|
+
const profile = parseProfile(options.profile, issues);
|
|
15
|
+
const steps = profile === 'core' && issues.length === 0 ? createCoreSteps(options.paths, issues) : [];
|
|
16
|
+
if (profile === 'release-readiness' && issues.length === 0) {
|
|
17
|
+
issues.push({
|
|
18
|
+
severity: 'error',
|
|
19
|
+
code: 'FEATURE_SMOKE_PROFILE_DEFERRED',
|
|
20
|
+
message: 'The release-readiness smoke profile is deferred until package, install matrix, and release artifact evidence exist.'
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
const report = {
|
|
24
|
+
schemaVersion: 'hadara.featureSmoke.v1',
|
|
25
|
+
command: 'feature-smoke.run',
|
|
26
|
+
ok: issues.every((issue) => issue.severity !== 'error') && steps.every((step) => step.status === 'passed'),
|
|
27
|
+
profile,
|
|
28
|
+
readOnly: true,
|
|
29
|
+
executionMode: 'service-read-model',
|
|
30
|
+
binaryExecuted: false,
|
|
31
|
+
launcherChecked: false,
|
|
32
|
+
packageInstallChecked: false,
|
|
33
|
+
steps,
|
|
34
|
+
issues
|
|
35
|
+
};
|
|
36
|
+
(0, schema_1.assertSchema)('hadara.featureSmoke.v1', report);
|
|
37
|
+
return report;
|
|
38
|
+
}
|
|
39
|
+
function parseProfile(value, issues) {
|
|
40
|
+
if (value === undefined || value === 'core')
|
|
41
|
+
return 'core';
|
|
42
|
+
if (value === 'release-readiness')
|
|
43
|
+
return 'release-readiness';
|
|
44
|
+
issues.push({
|
|
45
|
+
severity: 'error',
|
|
46
|
+
code: 'FEATURE_SMOKE_PROFILE_UNSUPPORTED',
|
|
47
|
+
message: `Unsupported feature smoke profile: ${value}`
|
|
48
|
+
});
|
|
49
|
+
return 'core';
|
|
50
|
+
}
|
|
51
|
+
function createCoreSteps(paths, issues) {
|
|
52
|
+
return [
|
|
53
|
+
runStep(issues, {
|
|
54
|
+
id: 'doctor',
|
|
55
|
+
command: 'hadara doctor --json',
|
|
56
|
+
schemaVersion: 'hadara.doctor.v1',
|
|
57
|
+
run: () => (0, doctor_1.createDoctorReport)(paths),
|
|
58
|
+
summarize: (report) => `Doctor completed with ${report.checks.length} path checks.`
|
|
59
|
+
}),
|
|
60
|
+
runStep(issues, {
|
|
61
|
+
id: 'status',
|
|
62
|
+
command: 'hadara status --json',
|
|
63
|
+
schemaVersion: 'hadara.ops.status.v1',
|
|
64
|
+
run: () => (0, operations_status_service_1.createOpsStatusReport)(paths.projectRoot),
|
|
65
|
+
summarize: (report) => `Operations status completed with health ${report.health}.`
|
|
66
|
+
}),
|
|
67
|
+
runStep(issues, {
|
|
68
|
+
id: 'task-list',
|
|
69
|
+
command: 'hadara task list --json',
|
|
70
|
+
schemaVersion: 'hadara.task.list.v1',
|
|
71
|
+
run: () => (0, task_read_model_1.createTaskListReport)(paths.projectRoot),
|
|
72
|
+
summarize: (report) => `Task list completed with ${report.count} tasks.`
|
|
73
|
+
}),
|
|
74
|
+
runStep(issues, {
|
|
75
|
+
id: 'tools-list',
|
|
76
|
+
command: 'hadara tools list --json',
|
|
77
|
+
schemaVersion: 'hadara.tools.list.v1',
|
|
78
|
+
run: () => (0, tools_list_1.createToolsListReport)(),
|
|
79
|
+
summarize: (report) => `Tools list completed with ${report.surfaces.cli.length} CLI surfaces.`
|
|
80
|
+
}),
|
|
81
|
+
runStep(issues, {
|
|
82
|
+
id: 'tui-snapshot',
|
|
83
|
+
command: 'hadara tui --snapshot --json',
|
|
84
|
+
schemaVersion: 'hadara.tui.snapshot.cli.v1',
|
|
85
|
+
run: () => {
|
|
86
|
+
(0, snapshot_1.renderTuiSnapshot)((0, read_model_1.createTuiReadModel)(paths.projectRoot), { width: 86, height: 24, widthPolicy: 'compact', theme: 'none' });
|
|
87
|
+
return { ok: true };
|
|
88
|
+
},
|
|
89
|
+
summarize: () => 'TUI snapshot completed as a reduced no-color render.'
|
|
90
|
+
}),
|
|
91
|
+
runStep(issues, {
|
|
92
|
+
id: 'release-gate-advisory',
|
|
93
|
+
command: 'hadara release gate --mode advisory --json',
|
|
94
|
+
schemaVersion: 'hadara.releaseGate.v1',
|
|
95
|
+
run: () => (0, operational_debt_1.createReleaseGateReport)(paths.projectRoot, 'advisory'),
|
|
96
|
+
summarize: (report) => `Advisory release gate completed with ${report.checks.length} checks.`
|
|
97
|
+
})
|
|
98
|
+
];
|
|
99
|
+
}
|
|
100
|
+
function runStep(issues, step) {
|
|
101
|
+
try {
|
|
102
|
+
const result = step.run();
|
|
103
|
+
const schemaStatus = validateRegisteredStepSchema(step.schemaVersion, result);
|
|
104
|
+
if (schemaStatus === 'invalid') {
|
|
105
|
+
issues.push({
|
|
106
|
+
severity: 'error',
|
|
107
|
+
code: 'FEATURE_SMOKE_STEP_SCHEMA_INVALID',
|
|
108
|
+
message: `${step.command} produced a reduced report that failed its registered schema.`,
|
|
109
|
+
stepId: step.id
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
if (!result.ok) {
|
|
113
|
+
issues.push({
|
|
114
|
+
severity: 'error',
|
|
115
|
+
code: 'FEATURE_SMOKE_STEP_FAILED',
|
|
116
|
+
message: `${step.command} returned a non-ok reduced report.`,
|
|
117
|
+
stepId: step.id
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
return {
|
|
121
|
+
id: step.id,
|
|
122
|
+
command: step.command,
|
|
123
|
+
executionMode: 'service-read-model',
|
|
124
|
+
status: result.ok && schemaStatus !== 'invalid' ? 'passed' : 'failed',
|
|
125
|
+
schemaVersion: step.schemaVersion,
|
|
126
|
+
schemaStatus,
|
|
127
|
+
summary: step.summarize(result)
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
catch {
|
|
131
|
+
issues.push({
|
|
132
|
+
severity: 'error',
|
|
133
|
+
code: 'FEATURE_SMOKE_STEP_THREW',
|
|
134
|
+
message: `${step.command} failed before producing a reduced report.`,
|
|
135
|
+
stepId: step.id
|
|
136
|
+
});
|
|
137
|
+
return {
|
|
138
|
+
id: step.id,
|
|
139
|
+
command: step.command,
|
|
140
|
+
executionMode: 'service-read-model',
|
|
141
|
+
status: 'failed',
|
|
142
|
+
schemaVersion: step.schemaVersion,
|
|
143
|
+
summary: 'Step failed before producing a reduced report.'
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
function validateRegisteredStepSchema(schemaVersion, value) {
|
|
148
|
+
try {
|
|
149
|
+
const result = (0, schema_1.validateSchema)(schemaVersion, value);
|
|
150
|
+
return result.ok ? 'validated' : 'invalid';
|
|
151
|
+
}
|
|
152
|
+
catch {
|
|
153
|
+
return 'not-registered';
|
|
154
|
+
}
|
|
155
|
+
}
|