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,25 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createPolicyCheckReport = createPolicyCheckReport;
4
+ exports.createPolicyEvaluateReport = createPolicyEvaluateReport;
5
+ const preflight_1 = require("../policy/preflight");
6
+ const policy_1 = require("../policy/policy");
7
+ function createPolicyCheckReport(command, mode = 'assisted') {
8
+ const normalizedMode = (0, policy_1.parsePermissionMode)(mode);
9
+ const shell = (0, policy_1.tokenizeShellCommand)(command);
10
+ const decision = (0, policy_1.classifyShellCommand)(command, normalizedMode);
11
+ return {
12
+ schemaVersion: 'hadara.policy.check-shell.v1',
13
+ command: 'policy.check-shell',
14
+ ok: decision.action !== 'deny',
15
+ input: {
16
+ mode: normalizedMode,
17
+ command
18
+ },
19
+ shell,
20
+ decision
21
+ };
22
+ }
23
+ function createPolicyEvaluateReport(command, mode = 'assisted') {
24
+ return (0, preflight_1.createShellExecutionPreflight)(command, (0, policy_1.parsePermissionMode)(mode));
25
+ }
@@ -0,0 +1,101 @@
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.readProjectFile = readProjectFile;
7
+ exports.readProjectSources = readProjectSources;
8
+ exports.createHandoffReadReport = createHandoffReadReport;
9
+ exports.createProjectStateReadReport = createProjectStateReadReport;
10
+ exports.tailLines = tailLines;
11
+ exports.extractSection = extractSection;
12
+ const node_fs_1 = __importDefault(require("node:fs"));
13
+ const node_path_1 = __importDefault(require("node:path"));
14
+ function readProjectFile(projectRoot, relativePath) {
15
+ const filePath = node_path_1.default.join(projectRoot, relativePath);
16
+ const exists = node_fs_1.default.existsSync(filePath);
17
+ return {
18
+ path: relativePath,
19
+ exists,
20
+ content: exists ? node_fs_1.default.readFileSync(filePath, 'utf8') : ''
21
+ };
22
+ }
23
+ function readProjectSources(projectRoot) {
24
+ return {
25
+ projectState: readProjectFile(projectRoot, 'docs/PROJECT_STATE.md'),
26
+ handoff: readProjectFile(projectRoot, 'docs/AGENT_HANDOFF.md'),
27
+ taskBoard: readProjectFile(projectRoot, 'docs/TASK_BOARD.md'),
28
+ developmentSlices: readProjectFile(projectRoot, 'docs/DEVELOPMENT_SLICES.md'),
29
+ validationHistory: readProjectFile(projectRoot, 'docs/VALIDATION_HISTORY.md')
30
+ };
31
+ }
32
+ function createHandoffReadReport(projectRoot, options) {
33
+ const sources = readProjectSources(projectRoot);
34
+ return {
35
+ schemaVersion: 'hadara.handoff.read.v1',
36
+ command: 'handoff.read',
37
+ ok: true,
38
+ handoff: {
39
+ current: sources.handoff.content,
40
+ history: options.includeHistory ? tailLines(readProjectFile(projectRoot, 'docs/HANDOFF_HISTORY.md').content, options.historyLimit) : null,
41
+ validationHistory: options.includeHistory ? tailLines(sources.validationHistory.content, options.historyLimit) : null
42
+ },
43
+ issues: []
44
+ };
45
+ }
46
+ function createProjectStateReadReport(projectRoot, options) {
47
+ const sources = readProjectSources(projectRoot);
48
+ if (options.summaryOnly) {
49
+ return {
50
+ schemaVersion: 'hadara.project.state.read.v1',
51
+ command: 'project.state.read',
52
+ ok: true,
53
+ summary: {
54
+ projectState: extractSection(sources.projectState.content, '## Current Status'),
55
+ taskBoardTail: tailLines(sources.taskBoard.content, 20),
56
+ developmentSlicesTail: tailLines(sources.developmentSlices.content, 12)
57
+ },
58
+ issues: []
59
+ };
60
+ }
61
+ if (!options.includeDocuments) {
62
+ return {
63
+ schemaVersion: 'hadara.project.state.read.v1',
64
+ command: 'project.state.read',
65
+ ok: true,
66
+ documents: [
67
+ { path: sources.projectState.path, included: false },
68
+ { path: sources.taskBoard.path, included: false },
69
+ { path: sources.developmentSlices.path, included: false }
70
+ ],
71
+ issues: []
72
+ };
73
+ }
74
+ return {
75
+ schemaVersion: 'hadara.project.state.read.v1',
76
+ command: 'project.state.read',
77
+ ok: true,
78
+ projectState: sources.projectState.content,
79
+ taskBoard: sources.taskBoard.content,
80
+ developmentSlices: sources.developmentSlices.content,
81
+ issues: []
82
+ };
83
+ }
84
+ function tailLines(content, limit) {
85
+ return content
86
+ .split(/\r?\n/)
87
+ .filter((line) => line.trim().length > 0)
88
+ .slice(-limit)
89
+ .join('\n');
90
+ }
91
+ function extractSection(content, heading) {
92
+ const match = new RegExp(`^${escapeRegExp(heading)}\\s*$`, 'm').exec(content);
93
+ if (!match || match.index === undefined)
94
+ return '';
95
+ const afterHeading = content.slice(match.index + match[0].length);
96
+ const nextHeading = afterHeading.search(/\n##\s+/);
97
+ return (nextHeading >= 0 ? afterHeading.slice(0, nextHeading) : afterHeading).trim();
98
+ }
99
+ function escapeRegExp(value) {
100
+ return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
101
+ }
@@ -0,0 +1,77 @@
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.attachReleaseArtifactEvidence = attachReleaseArtifactEvidence;
7
+ const node_fs_1 = __importDefault(require("node:fs"));
8
+ const node_path_1 = __importDefault(require("node:path"));
9
+ const evidence_1 = require("../evidence/evidence");
10
+ const fs_1 = require("../core/fs");
11
+ const redaction_1 = require("../core/redaction");
12
+ const release_dry_run_1 = require("./release-dry-run");
13
+ function attachReleaseArtifactEvidence(input) {
14
+ const taskDir = findTaskDir(input.projectRoot, input.taskId);
15
+ if (!taskDir)
16
+ throw new Error(`Task capsule not found: ${input.taskId}`);
17
+ const time = new Date().toISOString();
18
+ const reportArtifact = {
19
+ ...input.report,
20
+ evidence: {
21
+ time,
22
+ taskId: input.taskId,
23
+ gitCommit: (0, release_dry_run_1.readCurrentGitCommit)(input.projectRoot)
24
+ }
25
+ };
26
+ const content = JSON.stringify(reportArtifact, null, 2);
27
+ const policy = (0, evidence_1.createPublicEvidenceArtifactPolicyReport)(content);
28
+ if (policy.blocking) {
29
+ throw new evidence_1.EvidenceArtifactPolicyError('PUBLIC_ARTIFACT_SECRET_DETECTED', 'Public release artifact evidence contains secret-like content; reduce the report before attaching.', policy.redaction);
30
+ }
31
+ const artifactDir = node_path_1.default.join(taskDir, 'artifacts', 'release-artifact');
32
+ (0, fs_1.ensureDir)(artifactDir);
33
+ const targetPath = node_path_1.default.join(artifactDir, `${safeFilePart(time)}-report.json`);
34
+ node_fs_1.default.writeFileSync(targetPath, content, 'utf8');
35
+ const evidencePath = toPortablePath(node_path_1.default.relative(taskDir, targetPath));
36
+ const evidence = {
37
+ schemaVersion: 'hadara.evidence.v1',
38
+ time,
39
+ taskId: input.taskId,
40
+ kind: 'command-log',
41
+ summary: (0, redaction_1.redactSecrets)(input.summary.replace(/\|/g, '/')),
42
+ result: input.report.ok ? 'passed' : 'failed',
43
+ visibility: 'public',
44
+ evidencePath
45
+ };
46
+ appendEvidenceMarkdown(taskDir, evidence);
47
+ node_fs_1.default.appendFileSync(node_path_1.default.join(taskDir, 'evidence.jsonl'), `${JSON.stringify(evidence)}\n`, 'utf8');
48
+ return {
49
+ evidence,
50
+ artifact: {
51
+ kind: 'report',
52
+ visibility: 'public',
53
+ evidencePath: toPortablePath(node_path_1.default.join('tasks', node_path_1.default.basename(taskDir), evidencePath)),
54
+ rawContentIncluded: false
55
+ }
56
+ };
57
+ }
58
+ function appendEvidenceMarkdown(taskDir, evidence) {
59
+ const markdownPath = node_path_1.default.join(taskDir, 'EVIDENCE.md');
60
+ if (!node_fs_1.default.existsSync(markdownPath)) {
61
+ node_fs_1.default.writeFileSync(markdownPath, '# Evidence\n\n| Time | Kind | Summary | Result |\n|---|---|---|---|\n', 'utf8');
62
+ }
63
+ node_fs_1.default.appendFileSync(markdownPath, `| ${evidence.time} | ${evidence.kind} | ${evidence.summary} (${evidence.evidencePath}) | ${evidence.result} |\n`, 'utf8');
64
+ }
65
+ function findTaskDir(projectRoot, taskId) {
66
+ const tasksDir = node_path_1.default.join(projectRoot, 'tasks');
67
+ if (!node_fs_1.default.existsSync(tasksDir))
68
+ return null;
69
+ const entry = node_fs_1.default.readdirSync(tasksDir).find((name) => name.startsWith(`${taskId}-`));
70
+ return entry ? node_path_1.default.join(tasksDir, entry) : null;
71
+ }
72
+ function safeFilePart(value) {
73
+ return value.replace(/[^A-Za-z0-9._-]+/g, '-').replace(/^-+|-+$/g, '') || 'artifact';
74
+ }
75
+ function toPortablePath(value) {
76
+ return value.split(node_path_1.default.sep).join('/');
77
+ }
@@ -0,0 +1,351 @@
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.createReleaseArtifactReport = createReleaseArtifactReport;
7
+ const node_crypto_1 = __importDefault(require("node:crypto"));
8
+ const node_fs_1 = __importDefault(require("node:fs"));
9
+ const node_os_1 = __importDefault(require("node:os"));
10
+ const node_path_1 = __importDefault(require("node:path"));
11
+ const node_child_process_1 = require("node:child_process");
12
+ const fs_1 = require("../core/fs");
13
+ const schema_1 = require("../core/schema");
14
+ const requiredFiles = ['package.json', 'README.md', 'LICENSE', 'dist/cli/main.js'];
15
+ const allowedRoots = ['dist/', 'README.md', 'LICENSE', 'package.json'];
16
+ function createReleaseArtifactReport(options) {
17
+ const issues = [];
18
+ validateTimeout(options.timeoutSeconds, issues);
19
+ if (options.execute !== true) {
20
+ issues.push({
21
+ severity: 'error',
22
+ code: 'RELEASE_ARTIFACT_EXECUTION_REQUIRED',
23
+ message: 'Release artifact building requires explicit --execute because it runs npm pack and writes artifacts.'
24
+ });
25
+ }
26
+ const packageMetadata = readPackageMetadata(options.paths.projectRoot, issues);
27
+ const output = prepareOutput(options.paths.projectRoot, options.output, options.keepTemp === true, issues);
28
+ const staging = prepareStaging(options.paths.projectRoot, issues);
29
+ const runner = options.runner ?? runCommand;
30
+ const timeoutMs = (options.timeoutSeconds ?? 120) * 1000;
31
+ const execution = {
32
+ stagingCreated: false,
33
+ npmPackExecuted: false,
34
+ checksumGenerated: false,
35
+ manifestGenerated: false,
36
+ packageContentsVerified: false,
37
+ publishExecuted: false,
38
+ githubReleaseCreated: false,
39
+ dockerImageBuilt: false
40
+ };
41
+ const artifacts = [];
42
+ const packageContents = {
43
+ verified: false,
44
+ fileCount: 0,
45
+ allowedRoots,
46
+ requiredFiles,
47
+ forbiddenMatches: []
48
+ };
49
+ try {
50
+ if (!issues.some((issue) => issue.severity === 'error')) {
51
+ copyWhitelistedPackage(options.paths.projectRoot, staging.path, packageMetadata, issues);
52
+ execution.stagingCreated = !issues.some((issue) => issue.stepId === 'stage-package');
53
+ if (execution.stagingCreated) {
54
+ execution.npmPackExecuted = true;
55
+ const pack = runner(npmCommand(), ['pack', '--json', '--pack-destination', output.path], {
56
+ cwd: staging.path,
57
+ timeoutMs
58
+ });
59
+ const parsed = parsePackResult(pack.stdout);
60
+ if (pack.status !== 0 || !parsed) {
61
+ issues.push({
62
+ severity: 'error',
63
+ code: pack.timedOut ? 'RELEASE_ARTIFACT_NPM_PACK_TIMEOUT' : 'RELEASE_ARTIFACT_NPM_PACK_FAILED',
64
+ message: pack.timedOut ? 'npm pack timed out while building release artifacts.' : 'npm pack failed or did not return a reduced package result.',
65
+ stepId: 'npm-pack'
66
+ });
67
+ }
68
+ else {
69
+ const verify = verifyPackageContents(parsed.files.map((file) => file.path));
70
+ packageContents.verified = verify.ok;
71
+ packageContents.fileCount = parsed.files.length;
72
+ packageContents.forbiddenMatches = verify.forbidden;
73
+ execution.packageContentsVerified = verify.ok;
74
+ for (const missing of verify.missingRequired) {
75
+ issues.push({
76
+ severity: 'error',
77
+ code: 'RELEASE_ARTIFACT_REQUIRED_FILE_MISSING',
78
+ message: `Release artifact package is missing required file ${missing}.`,
79
+ stepId: 'verify-contents'
80
+ });
81
+ }
82
+ for (const forbidden of verify.forbidden) {
83
+ issues.push({
84
+ severity: 'error',
85
+ code: 'RELEASE_ARTIFACT_FORBIDDEN_FILE_INCLUDED',
86
+ message: `Release artifact package includes file outside the whitelist: ${forbidden}.`,
87
+ stepId: 'verify-contents'
88
+ });
89
+ }
90
+ const tarballPath = node_path_1.default.join(output.path, parsed.filename);
91
+ const tarballHash = hashFile(tarballPath);
92
+ artifacts.push(createArtifact('tarball', output, parsed.filename, tarballPath, tarballHash));
93
+ const checksumFileName = `${parsed.filename}.sha256`;
94
+ node_fs_1.default.writeFileSync(node_path_1.default.join(output.path, checksumFileName), `${tarballHash} ${parsed.filename}\n`, 'utf8');
95
+ execution.checksumGenerated = true;
96
+ artifacts.push(createArtifact('checksum', output, checksumFileName, node_path_1.default.join(output.path, checksumFileName), hashFile(node_path_1.default.join(output.path, checksumFileName))));
97
+ const manifestFileName = `${parsed.filename}.manifest.json`;
98
+ const manifest = createManifest(packageMetadata, parsed, tarballHash);
99
+ node_fs_1.default.writeFileSync(node_path_1.default.join(output.path, manifestFileName), `${JSON.stringify(manifest, null, 2)}\n`, 'utf8');
100
+ execution.manifestGenerated = true;
101
+ artifacts.push(createArtifact('manifest', output, manifestFileName, node_path_1.default.join(output.path, manifestFileName), hashFile(node_path_1.default.join(output.path, manifestFileName))));
102
+ }
103
+ }
104
+ }
105
+ }
106
+ finally {
107
+ cleanupDirectory(staging.path, true);
108
+ if (output.cleanupAllowed)
109
+ cleanupDirectory(output.path, false);
110
+ }
111
+ const report = {
112
+ schemaVersion: 'hadara.releaseArtifact.v1',
113
+ command: 'release.artifact',
114
+ ok: issues.every((issue) => issue.severity !== 'error'),
115
+ mode: 'execute',
116
+ execution,
117
+ output: {
118
+ kind: output.kind,
119
+ displayPath: output.displayPath,
120
+ pathRedacted: true,
121
+ ...(output.relativePath ? { relativePath: output.relativePath } : {}),
122
+ retention: output.retention
123
+ },
124
+ package: {
125
+ name: packageMetadata.name,
126
+ version: packageMetadata.version,
127
+ private: packageMetadata.private,
128
+ filesWhitelistApplied: true
129
+ },
130
+ artifacts,
131
+ packageContents,
132
+ privacy: {
133
+ rawLogsIncluded: false,
134
+ packageContentsIncluded: false,
135
+ privatePathsIncluded: false,
136
+ environmentSecretsIncluded: false,
137
+ privateStorePathsIncluded: false
138
+ },
139
+ issues
140
+ };
141
+ (0, schema_1.assertSchema)('hadara.releaseArtifact.v1', report);
142
+ return report;
143
+ }
144
+ function readPackageMetadata(projectRoot, issues) {
145
+ try {
146
+ const parsed = JSON.parse(node_fs_1.default.readFileSync(node_path_1.default.join(projectRoot, 'package.json'), 'utf8'));
147
+ const bin = isRecord(parsed.bin) ? parsed.bin : {};
148
+ if (parsed.name !== 'hadara') {
149
+ issues.push({ severity: 'error', code: 'RELEASE_ARTIFACT_PACKAGE_NAME_INVALID', message: 'Release artifact package name must be hadara.' });
150
+ }
151
+ if (bin.hadara !== './dist/cli/main.js') {
152
+ issues.push({ severity: 'error', code: 'RELEASE_ARTIFACT_BIN_MISSING', message: 'Release artifact package must expose bin.hadara at ./dist/cli/main.js.' });
153
+ }
154
+ return {
155
+ name: typeof parsed.name === 'string' ? parsed.name : 'unknown',
156
+ version: typeof parsed.version === 'string' ? parsed.version : '0.0.0',
157
+ private: parsed.private === true,
158
+ ...(typeof parsed.license === 'string' ? { license: parsed.license } : {})
159
+ };
160
+ }
161
+ catch {
162
+ issues.push({
163
+ severity: 'error',
164
+ code: 'RELEASE_ARTIFACT_PACKAGE_JSON_MISSING',
165
+ message: 'Release artifact builder requires readable package.json metadata.'
166
+ });
167
+ return { name: 'unknown', version: '0.0.0', private: true };
168
+ }
169
+ }
170
+ function prepareOutput(projectRoot, output, keepTemp, issues) {
171
+ const outputPath = output ? node_path_1.default.resolve(projectRoot, output) : node_fs_1.default.mkdtempSync(node_path_1.default.join(node_os_1.default.tmpdir(), 'hadara-release-artifact-'));
172
+ try {
173
+ (0, fs_1.ensureDir)(outputPath);
174
+ }
175
+ catch {
176
+ issues.push({
177
+ severity: 'error',
178
+ code: 'RELEASE_ARTIFACT_OUTPUT_CREATE_FAILED',
179
+ message: 'Release artifact output directory could not be created.'
180
+ });
181
+ }
182
+ const relativePath = safeRelativePath(output);
183
+ return {
184
+ kind: output ? 'explicit' : 'disposable',
185
+ path: outputPath,
186
+ displayPath: relativePath ? `./${relativePath}` : '<redacted-release-artifact-output>',
187
+ ...(relativePath ? { relativePath } : {}),
188
+ retention: output ? 'explicit-output' : keepTemp ? 'kept-temporary' : 'deleted',
189
+ cleanupAllowed: !output && !keepTemp
190
+ };
191
+ }
192
+ function prepareStaging(projectRoot, issues) {
193
+ const staging = node_fs_1.default.mkdtempSync(node_path_1.default.join(node_os_1.default.tmpdir(), 'hadara-release-artifact-stage-'));
194
+ for (const required of ['dist/cli/main.js', 'README.md', 'LICENSE']) {
195
+ if (!node_fs_1.default.existsSync(node_path_1.default.join(projectRoot, required))) {
196
+ issues.push({
197
+ severity: 'error',
198
+ code: 'RELEASE_ARTIFACT_REQUIRED_SOURCE_MISSING',
199
+ message: `Release artifact source is missing required file ${required}.`,
200
+ stepId: 'stage-package'
201
+ });
202
+ }
203
+ }
204
+ return { path: staging };
205
+ }
206
+ function copyWhitelistedPackage(projectRoot, staging, metadata, issues) {
207
+ try {
208
+ node_fs_1.default.cpSync(node_path_1.default.join(projectRoot, 'dist'), node_path_1.default.join(staging, 'dist'), { recursive: true });
209
+ node_fs_1.default.copyFileSync(node_path_1.default.join(projectRoot, 'README.md'), node_path_1.default.join(staging, 'README.md'));
210
+ node_fs_1.default.copyFileSync(node_path_1.default.join(projectRoot, 'LICENSE'), node_path_1.default.join(staging, 'LICENSE'));
211
+ node_fs_1.default.writeFileSync(node_path_1.default.join(staging, 'package.json'), `${JSON.stringify({
212
+ name: metadata.name,
213
+ version: metadata.version,
214
+ private: metadata.private,
215
+ ...(metadata.license ? { license: metadata.license } : {}),
216
+ description: 'HADARA: portable agentic development workbench',
217
+ bin: { hadara: './dist/cli/main.js' },
218
+ files: ['dist/', 'README.md', 'LICENSE', 'package.json']
219
+ }, null, 2)}\n`, 'utf8');
220
+ }
221
+ catch {
222
+ issues.push({
223
+ severity: 'error',
224
+ code: 'RELEASE_ARTIFACT_STAGE_FAILED',
225
+ message: 'Release artifact staging package could not be created.',
226
+ stepId: 'stage-package'
227
+ });
228
+ }
229
+ }
230
+ function parsePackResult(stdout) {
231
+ try {
232
+ const parsed = JSON.parse(stdout);
233
+ if (!Array.isArray(parsed))
234
+ return undefined;
235
+ const first = parsed[0];
236
+ if (!first || typeof first.filename !== 'string' || first.filename.includes('/') || first.filename.includes('\\'))
237
+ return undefined;
238
+ const files = Array.isArray(first.files)
239
+ ? first.files
240
+ .map((file) => {
241
+ if (!isRecord(file) || typeof file.path !== 'string')
242
+ return undefined;
243
+ return {
244
+ path: file.path,
245
+ ...(typeof file.size === 'number' ? { size: file.size } : {})
246
+ };
247
+ })
248
+ .filter((file) => file !== undefined)
249
+ : [];
250
+ return { filename: first.filename, files };
251
+ }
252
+ catch {
253
+ return undefined;
254
+ }
255
+ }
256
+ function verifyPackageContents(files) {
257
+ const forbidden = files.filter((file) => !allowedRoots.some((root) => file === root || (root.endsWith('/') && file.startsWith(root))));
258
+ const missingRequired = requiredFiles.filter((required) => !files.includes(required));
259
+ return {
260
+ ok: forbidden.length === 0 && missingRequired.length === 0,
261
+ forbidden,
262
+ missingRequired
263
+ };
264
+ }
265
+ function createManifest(metadata, pack, tarballHash) {
266
+ return {
267
+ schemaVersion: 'hadara.releaseArtifact.manifest.v1',
268
+ package: metadata,
269
+ tarball: {
270
+ fileName: pack.filename,
271
+ hash: `sha256:${tarballHash}`
272
+ },
273
+ files: pack.files.map((file) => ({
274
+ path: file.path,
275
+ ...(file.size === undefined ? {} : { byteLength: file.size })
276
+ })),
277
+ releaseMutationExecuted: false,
278
+ publishExecuted: false,
279
+ githubReleaseCreated: false
280
+ };
281
+ }
282
+ function createArtifact(kind, output, fileName, filePath, hash) {
283
+ return {
284
+ kind,
285
+ visibility: output.kind === 'explicit' ? 'local' : 'temporary',
286
+ fileName,
287
+ ...(output.relativePath ? { relativePath: `${output.relativePath}/${fileName}` } : {}),
288
+ pathRedacted: true,
289
+ byteLength: safeFileSize(filePath),
290
+ hash: `sha256:${hash}`,
291
+ rawContentIncluded: false
292
+ };
293
+ }
294
+ function hashFile(filePath) {
295
+ const hash = node_crypto_1.default.createHash('sha256');
296
+ hash.update(node_fs_1.default.readFileSync(filePath));
297
+ return hash.digest('hex');
298
+ }
299
+ function safeFileSize(filePath) {
300
+ try {
301
+ return node_fs_1.default.statSync(filePath).size;
302
+ }
303
+ catch {
304
+ return 0;
305
+ }
306
+ }
307
+ function cleanupDirectory(directory, cleanup) {
308
+ if (cleanup)
309
+ node_fs_1.default.rmSync(directory, { recursive: true, force: true });
310
+ }
311
+ function runCommand(command, args, options) {
312
+ const started = Date.now();
313
+ const result = (0, node_child_process_1.spawnSync)(command, args, {
314
+ cwd: options.cwd,
315
+ encoding: 'utf8',
316
+ timeout: options.timeoutMs,
317
+ maxBuffer: 1024 * 1024 * 4
318
+ });
319
+ return {
320
+ status: typeof result.status === 'number' ? result.status : null,
321
+ signal: result.signal,
322
+ stdout: result.stdout ?? '',
323
+ stderr: result.stderr ?? '',
324
+ elapsedMs: Date.now() - started,
325
+ timedOut: result.error?.name === 'TimeoutError' || result.signal === 'SIGTERM'
326
+ };
327
+ }
328
+ function validateTimeout(timeoutSeconds, issues) {
329
+ if (timeoutSeconds !== undefined && (!Number.isSafeInteger(timeoutSeconds) || timeoutSeconds < 1)) {
330
+ issues.push({
331
+ severity: 'error',
332
+ code: 'RELEASE_ARTIFACT_TIMEOUT_INVALID',
333
+ message: 'Release artifact timeout must be a positive integer number of seconds.'
334
+ });
335
+ }
336
+ }
337
+ function npmCommand() {
338
+ return process.platform === 'win32' ? 'npm.cmd' : 'npm';
339
+ }
340
+ function safeRelativePath(value) {
341
+ if (!value || node_path_1.default.isAbsolute(value) || /^[A-Za-z]:[\\/]/.test(value) || value.startsWith('~') || value.startsWith('<') || value.includes('%')) {
342
+ return undefined;
343
+ }
344
+ const normalized = value.split(/[\\/]+/).filter(Boolean).join('/');
345
+ if (!normalized || normalized === '.' || normalized.startsWith('..'))
346
+ return undefined;
347
+ return normalized;
348
+ }
349
+ function isRecord(value) {
350
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
351
+ }