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
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 ictseoyoungmin
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,109 @@
1
+ # HADARA
2
+
3
+ **HADARA** is a portable agentic development workbench.
4
+
5
+ > Unbroken Context, Verified Development.
6
+
7
+ HADARA is named from **Harness + Dara**. A harness safely binds and controls complex systems; Dara carries layered associations of holding, wisdom, durability, and continuity. HADARA binds non-deterministic LLM agent work into a production-oriented workflow through Task Capsules, Session Continuity, Policy Layers, Evidence Logs, and Handoff Protocols.
8
+
9
+ This repository is a **bootstrap skeleton** for developing HADARA using the HADARA protocol itself.
10
+
11
+ ## Quick Start
12
+
13
+ ```bash
14
+ npm install
15
+ npm run dev -- doctor
16
+ npm run dev -- task create "implement ProviderClient contract"
17
+ npm run dev -- task list
18
+ npm test
19
+ ```
20
+
21
+ Linux/macOS portable launcher:
22
+
23
+ ```bash
24
+ chmod +x ./start.sh
25
+ ./start.sh doctor
26
+ ```
27
+
28
+ Windows launcher:
29
+
30
+ ```bat
31
+ START.bat doctor
32
+ ```
33
+
34
+ ## Development Principle
35
+
36
+ HADARA development must dogfood the HADARA workflow:
37
+
38
+ 1. Read `docs/PROJECT_STATE.md`
39
+ 2. Read `docs/AGENT_HANDOFF.md`
40
+ 3. Read `docs/TASK_BOARD.md`
41
+ 4. Work inside a Task Capsule
42
+ 5. Attach evidence before marking work complete
43
+ 6. Update handoff before stopping
44
+
45
+ ## Store Separation
46
+
47
+ HADARA separates **portable runtime state** from **project-owned development state**.
48
+
49
+ ### Portable / USB Store
50
+
51
+ Located under the HADARA installation root:
52
+
53
+ ```text
54
+ data/
55
+ config/
56
+ secrets/
57
+ sessions/
58
+ logs/
59
+ audit/
60
+ cache/
61
+ exports/
62
+ ```
63
+
64
+ This is **not committed**. It is local, portable, and may live on a USB drive.
65
+
66
+ ### Project Repo Store
67
+
68
+ Located inside each project repository:
69
+
70
+ ```text
71
+ docs/
72
+ tasks/
73
+ .hadara/
74
+ AGENTS.md
75
+ .hermes.md
76
+ HERMES.md
77
+ ```
78
+
79
+ This is committed when it represents reproducible project state, conventions, or agent context.
80
+
81
+ ## Initial CLI Commands
82
+
83
+ ```bash
84
+ hadara init
85
+ hadara doctor
86
+ hadara task create "..."
87
+ hadara task list
88
+ hadara task show T-0001
89
+ hadara evidence collect --task T-0001
90
+ hadara handoff update --task T-0001
91
+ hadara hermes detect
92
+ hadara hermes export-context
93
+ hadara mcp serve
94
+ ```
95
+
96
+ Current CLI is a seed implementation. Full agent execution, provider integration, dashboard, and MCP server runtime are intentionally stubbed for later tasks.
97
+
98
+ ## Test Suites
99
+
100
+ ```bash
101
+ npm run test:unit
102
+ npm run test:contract
103
+ npm run test:harness
104
+ npm run check
105
+ ```
106
+
107
+ ## License
108
+
109
+ TBD. Recommended candidates: Apache-2.0 or MIT.
@@ -0,0 +1,50 @@
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.attachAgentLoopEvidence = attachAgentLoopEvidence;
7
+ const node_path_1 = __importDefault(require("node:path"));
8
+ const evidence_1 = require("../evidence/evidence");
9
+ function attachAgentLoopEvidence(projectRoot, result) {
10
+ if (!result.taskId)
11
+ return [];
12
+ const fakeShellSteps = result.steps.filter(isFakeShellStep);
13
+ if (fakeShellSteps.length === 0)
14
+ return [];
15
+ const summary = `Agent loop captured ${fakeShellSteps.length} fake-shell observation${fakeShellSteps.length === 1 ? '' : 's'}.`;
16
+ const appended = (0, evidence_1.appendEvidenceTextArtifact)(projectRoot, {
17
+ taskId: result.taskId,
18
+ kind: 'command-log',
19
+ summary,
20
+ result: result.ok ? 'passed' : 'failed',
21
+ visibility: 'public'
22
+ }, {
23
+ fileName: 'agent-loop-fake-shell-observations.jsonl',
24
+ content: `${fakeShellSteps.map((step) => JSON.stringify(toObservationRecord(step))).join('\n')}\n`
25
+ });
26
+ return [toAttachment(projectRoot, appended, summary, result.ok ? 'passed' : 'failed')];
27
+ }
28
+ function isFakeShellStep(step) {
29
+ return step.type === 'tool' && step.tool === 'fake_shell';
30
+ }
31
+ function toObservationRecord(step) {
32
+ return {
33
+ step: step.step,
34
+ tool: step.tool,
35
+ ok: step.ok,
36
+ observation: step.observation
37
+ };
38
+ }
39
+ function toAttachment(projectRoot, appended, summary, result) {
40
+ return {
41
+ kind: 'command-log',
42
+ summary,
43
+ result,
44
+ evidencePath: appended.evidence.evidencePath ?? '',
45
+ markdownPath: toPortablePath(node_path_1.default.relative(projectRoot, appended.markdownPath))
46
+ };
47
+ }
48
+ function toPortablePath(value) {
49
+ return value.split(node_path_1.default.sep).join('/');
50
+ }
@@ -0,0 +1,124 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.runAgentLoop = runAgentLoop;
4
+ const policy_1 = require("../policy/policy");
5
+ const fake_shell_1 = require("../tools/fake-shell");
6
+ async function runAgentLoop(input) {
7
+ const mode = (0, policy_1.parsePermissionMode)(input.mode ?? 'assisted');
8
+ const maxSteps = input.maxSteps ?? 6;
9
+ const fixtures = input.fakeShellFixtures ?? {};
10
+ const steps = [];
11
+ const issues = [];
12
+ const messages = [
13
+ {
14
+ role: 'system',
15
+ content: 'You are running inside HADARA deterministic harness mode. Request fake shell tools with JSON only.'
16
+ },
17
+ { role: 'user', content: input.request }
18
+ ];
19
+ for (let step = 1; step <= maxSteps; step += 1) {
20
+ let response;
21
+ try {
22
+ response = await input.provider.chat({ messages });
23
+ }
24
+ catch (error) {
25
+ issues.push({
26
+ severity: 'error',
27
+ code: 'AGENT_PROVIDER_FAILED',
28
+ message: normalizeProviderFailure(input.provider, error).message,
29
+ step
30
+ });
31
+ return {
32
+ schemaVersion: 'hadara.agent.loop.v1',
33
+ command: 'agent.loop',
34
+ ok: false,
35
+ ...(input.taskId ? { taskId: input.taskId } : {}),
36
+ mode,
37
+ request: input.request,
38
+ steps,
39
+ issues
40
+ };
41
+ }
42
+ steps.push({ step, type: 'assistant', ok: response.finishReason !== 'error', response });
43
+ messages.push({ role: 'assistant', content: response.content });
44
+ const toolRequest = parseFakeShellToolRequest(response.content);
45
+ if (!toolRequest) {
46
+ const toolFailed = steps.some((item) => item.type === 'tool' && !item.ok);
47
+ return {
48
+ schemaVersion: 'hadara.agent.loop.v1',
49
+ command: 'agent.loop',
50
+ ok: !toolFailed && issues.every((issue) => issue.severity !== 'error') && response.finishReason !== 'error',
51
+ ...(input.taskId ? { taskId: input.taskId } : {}),
52
+ mode,
53
+ request: input.request,
54
+ finalResponse: response.content,
55
+ steps,
56
+ issues
57
+ };
58
+ }
59
+ const observation = (0, fake_shell_1.runFakeShellCommand)({
60
+ command: toolRequest.command,
61
+ mode,
62
+ fixtures
63
+ });
64
+ steps.push({
65
+ step,
66
+ type: 'tool',
67
+ ok: observation.ok,
68
+ tool: 'fake_shell',
69
+ observation
70
+ });
71
+ messages.push({ role: 'tool', content: JSON.stringify(observation) });
72
+ if (!observation.ok) {
73
+ issues.push({
74
+ severity: 'error',
75
+ code: `FAKE_SHELL_${observation.result.status.toUpperCase()}`,
76
+ message: observation.result.reason ?? observation.result.stderr,
77
+ step
78
+ });
79
+ }
80
+ }
81
+ issues.push({
82
+ severity: 'error',
83
+ code: 'AGENT_LOOP_MAX_STEPS_EXCEEDED',
84
+ message: `Agent loop exceeded maxSteps (${maxSteps}) without a final response.`
85
+ });
86
+ return {
87
+ schemaVersion: 'hadara.agent.loop.v1',
88
+ command: 'agent.loop',
89
+ ok: false,
90
+ ...(input.taskId ? { taskId: input.taskId } : {}),
91
+ mode,
92
+ request: input.request,
93
+ steps,
94
+ issues
95
+ };
96
+ }
97
+ function parseFakeShellToolRequest(content) {
98
+ let parsed;
99
+ try {
100
+ parsed = JSON.parse(content);
101
+ }
102
+ catch {
103
+ return null;
104
+ }
105
+ if (!parsed || typeof parsed !== 'object')
106
+ return null;
107
+ const record = parsed;
108
+ if (record.type === 'tool_request' && record.tool === 'fake_shell' && typeof record.command === 'string') {
109
+ return {
110
+ type: 'tool_request',
111
+ tool: 'fake_shell',
112
+ command: record.command
113
+ };
114
+ }
115
+ return null;
116
+ }
117
+ function normalizeProviderFailure(provider, error) {
118
+ if (isProviderError(error))
119
+ return error;
120
+ return provider.normalizeError(error);
121
+ }
122
+ function isProviderError(error) {
123
+ return Boolean(error && typeof error === 'object' && 'provider' in error && 'code' in error && 'retriable' in error);
124
+ }
@@ -0,0 +1,70 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.CliArgsError = void 0;
4
+ exports.getStringOption = getStringOption;
5
+ exports.getRequiredStringOption = getRequiredStringOption;
6
+ exports.getIntegerOption = getIntegerOption;
7
+ exports.getFlag = getFlag;
8
+ exports.rejectMissingValue = rejectMissingValue;
9
+ exports.rejectValueThatLooksLikeFlag = rejectValueThatLooksLikeFlag;
10
+ class CliArgsError extends Error {
11
+ code;
12
+ constructor(code, message) {
13
+ super(message);
14
+ this.code = code;
15
+ this.name = 'CliArgsError';
16
+ }
17
+ }
18
+ exports.CliArgsError = CliArgsError;
19
+ function getStringOption(args, name, fallback) {
20
+ const index = args.indexOf(name);
21
+ if (index < 0)
22
+ return fallback;
23
+ const value = args[index + 1];
24
+ rejectMissingValue(name, value);
25
+ rejectValueThatLooksLikeFlag(name, value);
26
+ return value;
27
+ }
28
+ function getRequiredStringOption(args, name) {
29
+ const value = getStringOption(args, name);
30
+ if (value === undefined) {
31
+ throw new CliArgsError('CLI_OPTION_REQUIRED', `${name} is required`);
32
+ }
33
+ return value;
34
+ }
35
+ function getIntegerOption(args, name, bounds = {}) {
36
+ const fallback = bounds.fallback === undefined ? undefined : String(bounds.fallback);
37
+ const value = getStringOption(args, name, fallback);
38
+ if (value === undefined)
39
+ return undefined;
40
+ if (!/^\d+$/.test(value)) {
41
+ throw new CliArgsError('CLI_OPTION_INTEGER_INVALID', integerMessage(name, bounds));
42
+ }
43
+ const parsed = Number(value);
44
+ if (!Number.isSafeInteger(parsed) || (bounds.min !== undefined && parsed < bounds.min) || (bounds.max !== undefined && parsed > bounds.max)) {
45
+ throw new CliArgsError('CLI_OPTION_INTEGER_INVALID', integerMessage(name, bounds));
46
+ }
47
+ return parsed;
48
+ }
49
+ function getFlag(args, name) {
50
+ return args.includes(name);
51
+ }
52
+ function rejectMissingValue(name, value) {
53
+ if (value === undefined) {
54
+ throw new CliArgsError('CLI_OPTION_MISSING_VALUE', `${name} requires a value`);
55
+ }
56
+ }
57
+ function rejectValueThatLooksLikeFlag(name, value) {
58
+ if (value.startsWith('--')) {
59
+ throw new CliArgsError('CLI_OPTION_VALUE_LOOKS_LIKE_FLAG', `${name} value must not look like a flag`);
60
+ }
61
+ }
62
+ function integerMessage(name, bounds) {
63
+ if (bounds.min !== undefined && bounds.max !== undefined)
64
+ return `${name} must be an integer from ${bounds.min} to ${bounds.max}`;
65
+ if (bounds.min !== undefined)
66
+ return `${name} must be an integer greater than or equal to ${bounds.min}`;
67
+ if (bounds.max !== undefined)
68
+ return `${name} must be an integer less than or equal to ${bounds.max}`;
69
+ return `${name} must be an integer`;
70
+ }
@@ -0,0 +1,185 @@
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.handleDashboardCommand = handleDashboardCommand;
7
+ exports.serveDashboard = serveDashboard;
8
+ exports.createDashboardStaticResponse = createDashboardStaticResponse;
9
+ exports.createDashboardServerResponse = createDashboardServerResponse;
10
+ const node_fs_1 = __importDefault(require("node:fs"));
11
+ const node_http_1 = __importDefault(require("node:http"));
12
+ const node_path_1 = __importDefault(require("node:path"));
13
+ const active_run_state_1 = require("../services/active-run-state");
14
+ const evidence_list_1 = require("../services/evidence-list");
15
+ const operational_debt_1 = require("../services/operational-debt");
16
+ const operations_status_service_1 = require("../services/operations-status-service");
17
+ const task_read_model_1 = require("../services/task-read-model");
18
+ const args_1 = require("./args");
19
+ const DASHBOARD_HTML = node_path_1.default.join('docs', 'design', 'dashboard', 'index.html');
20
+ const DASHBOARD_FIXTURE = node_path_1.default.join('docs', 'design', 'fixtures', 'hadara.ops.status.sample.json');
21
+ function handleDashboardCommand(input) {
22
+ if (input.args[0] !== 'dashboard' || input.args[1] !== 'serve')
23
+ return false;
24
+ const host = (0, args_1.getStringOption)(input.args, '--host', '127.0.0.1');
25
+ const port = (0, args_1.getIntegerOption)(input.args, '--port', { fallback: 4173, min: 1, max: 65535 });
26
+ serveDashboard(input.projectRoot, { host, port });
27
+ return true;
28
+ }
29
+ function serveDashboard(projectRoot, options = {}) {
30
+ const host = options.host ?? '127.0.0.1';
31
+ const port = options.port ?? 4173;
32
+ const server = node_http_1.default.createServer((request, response) => {
33
+ let staticResponse;
34
+ try {
35
+ staticResponse = createDashboardServerResponse(projectRoot, request.url ?? '/', request.method ?? 'GET');
36
+ }
37
+ catch {
38
+ staticResponse = internalError();
39
+ }
40
+ response.writeHead(staticResponse.statusCode, staticResponse.headers);
41
+ response.end(staticResponse.body);
42
+ });
43
+ server.listen(port, host, () => {
44
+ const address = server.address();
45
+ const actualPort = typeof address === 'object' && address ? address.port : port;
46
+ // console.log(`[HADARA] Dashboard serving sample fixture at http://${host}:${actualPort}/dashboard/`);
47
+ console.log(`[HADARA] Dashboard serving at http://${host}:${actualPort}/dashboard/ with read-only APIs under /api/.`);
48
+ });
49
+ return server;
50
+ }
51
+ function createDashboardStaticResponse(projectRoot, requestUrl, method = 'GET') {
52
+ const normalizedMethod = method.toUpperCase();
53
+ if (normalizedMethod !== 'GET' && normalizedMethod !== 'HEAD')
54
+ return methodNotAllowed();
55
+ const pathname = safePathname(requestUrl);
56
+ if (!pathname)
57
+ return notFound();
58
+ const headOnly = normalizedMethod === 'HEAD';
59
+ if (pathname === '/' || pathname === '/dashboard' || pathname === '/dashboard/' || pathname === '/dashboard/index.html') {
60
+ return fileResponse(projectRoot, DASHBOARD_HTML, 'text/html; charset=utf-8', headOnly);
61
+ }
62
+ if (pathname === '/fixtures/hadara.ops.status.sample.json' || pathname === '/dashboard/fixtures/hadara.ops.status.sample.json') {
63
+ return fileResponse(projectRoot, DASHBOARD_FIXTURE, 'application/json; charset=utf-8', headOnly);
64
+ }
65
+ return notFound();
66
+ }
67
+ function createDashboardServerResponse(projectRoot, requestUrl, method = 'GET') {
68
+ try {
69
+ const apiResponse = createDashboardApiResponse(projectRoot, requestUrl, method);
70
+ if (apiResponse)
71
+ return apiResponse;
72
+ return createDashboardStaticResponse(projectRoot, requestUrl, method);
73
+ }
74
+ catch {
75
+ return internalError();
76
+ }
77
+ }
78
+ function createDashboardApiResponse(projectRoot, requestUrl, method) {
79
+ const normalizedMethod = method.toUpperCase();
80
+ if (normalizedMethod !== 'GET' && normalizedMethod !== 'HEAD')
81
+ return null;
82
+ const url = safeUrl(requestUrl);
83
+ if (!url || !url.pathname.startsWith('/api/'))
84
+ return null;
85
+ const headOnly = normalizedMethod === 'HEAD';
86
+ if (url.pathname === '/api/status')
87
+ return jsonResponse((0, operations_status_service_1.createOpsStatusReport)(projectRoot), headOnly);
88
+ if (url.pathname === '/api/tasks')
89
+ return jsonResponse((0, task_read_model_1.createTaskListReport)(projectRoot), headOnly);
90
+ if (url.pathname === '/api/active-run')
91
+ return jsonResponse((0, active_run_state_1.safeCreateActiveRunProjection)(projectRoot), headOnly);
92
+ if (url.pathname === '/api/debt')
93
+ return jsonResponse((0, operational_debt_1.createOperationalDebtReport)(projectRoot), headOnly);
94
+ if (url.pathname === '/api/evidence') {
95
+ const taskId = url.searchParams.get('taskId')?.trim();
96
+ if (!taskId) {
97
+ return jsonResponse({
98
+ schemaVersion: 'hadara.dashboard.api.error.v1',
99
+ command: 'dashboard.api',
100
+ ok: false,
101
+ issues: [
102
+ {
103
+ severity: 'error',
104
+ code: 'TASK_ID_REQUIRED',
105
+ message: 'Missing required query parameter: taskId.'
106
+ }
107
+ ]
108
+ }, headOnly, 400);
109
+ }
110
+ return jsonResponse((0, evidence_list_1.createEvidenceListReport)(projectRoot, { taskId }), headOnly);
111
+ }
112
+ return notFound();
113
+ }
114
+ function safePathname(requestUrl) {
115
+ const url = safeUrl(requestUrl);
116
+ return url?.pathname ?? null;
117
+ }
118
+ function safeUrl(requestUrl) {
119
+ if (/(^|\/)\.\.?($|[/?#])|%2e|%2f|\\/i.test(requestUrl))
120
+ return null;
121
+ try {
122
+ return new URL(requestUrl, 'http://hadara.local');
123
+ }
124
+ catch {
125
+ return null;
126
+ }
127
+ }
128
+ function fileResponse(projectRoot, relativePath, contentType, headOnly) {
129
+ const filePath = node_path_1.default.join(projectRoot, relativePath);
130
+ if (!node_fs_1.default.existsSync(filePath))
131
+ return notFound();
132
+ const body = node_fs_1.default.readFileSync(filePath, 'utf8');
133
+ return {
134
+ statusCode: 200,
135
+ headers: securityHeaders({
136
+ 'content-type': contentType,
137
+ 'content-length': String(Buffer.byteLength(body))
138
+ }),
139
+ body: headOnly ? '' : body
140
+ };
141
+ }
142
+ function jsonResponse(bodyValue, headOnly, statusCode = 200) {
143
+ const body = `${JSON.stringify(bodyValue, null, 2)}\n`;
144
+ return {
145
+ statusCode,
146
+ headers: securityHeaders({
147
+ 'content-type': 'application/json; charset=utf-8',
148
+ 'content-length': String(Buffer.byteLength(body))
149
+ }),
150
+ body: headOnly ? '' : body
151
+ };
152
+ }
153
+ function notFound() {
154
+ return {
155
+ statusCode: 404,
156
+ headers: securityHeaders({ 'content-type': 'text/plain; charset=utf-8' }),
157
+ body: 'Not found'
158
+ };
159
+ }
160
+ function methodNotAllowed() {
161
+ return {
162
+ statusCode: 405,
163
+ headers: securityHeaders({
164
+ 'content-type': 'text/plain; charset=utf-8',
165
+ allow: 'GET, HEAD'
166
+ }),
167
+ body: 'Method not allowed'
168
+ };
169
+ }
170
+ function internalError() {
171
+ return {
172
+ statusCode: 500,
173
+ headers: securityHeaders({ 'content-type': 'text/plain; charset=utf-8' }),
174
+ body: 'Internal server error'
175
+ };
176
+ }
177
+ function securityHeaders(headers) {
178
+ return {
179
+ ...headers,
180
+ 'cache-control': 'no-store',
181
+ 'content-security-policy': "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; connect-src 'self'; img-src 'self'; object-src 'none'; base-uri 'none'",
182
+ 'referrer-policy': 'no-referrer',
183
+ 'x-content-type-options': 'nosniff'
184
+ };
185
+ }
@@ -0,0 +1,41 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.handleDebtCommand = handleDebtCommand;
4
+ const operational_debt_1 = require("../services/operational-debt");
5
+ function handleDebtCommand(input) {
6
+ if (input.args[0] !== 'debt')
7
+ return false;
8
+ const sub = input.args[1];
9
+ if (sub === 'list') {
10
+ const report = (0, operational_debt_1.createOperationalDebtReport)(input.projectRoot);
11
+ if (input.jsonOutput) {
12
+ console.log(JSON.stringify(report, null, 2));
13
+ }
14
+ else {
15
+ for (const record of report.records) {
16
+ console.log(`${record.id} | ${record.severity} | ${record.status} | ${record.title}`);
17
+ }
18
+ }
19
+ return true;
20
+ }
21
+ if (sub === 'show') {
22
+ const id = input.args[2];
23
+ if (!id)
24
+ throw new Error('debt show requires an id');
25
+ const report = (0, operational_debt_1.createOperationalDebtShowReport)(input.projectRoot, id);
26
+ if (input.jsonOutput) {
27
+ console.log(JSON.stringify(report, null, 2));
28
+ }
29
+ else if (report.record) {
30
+ console.log(`${report.record.id} | ${report.record.severity} | ${report.record.status} | ${report.record.title}`);
31
+ console.log(`targetCapability: ${report.record.targetCapability}`);
32
+ }
33
+ else {
34
+ console.log(`[HADARA] ${report.issues[0]?.message ?? `Operational debt record not found: ${id}`}`);
35
+ }
36
+ if (!report.ok)
37
+ process.exitCode = 6;
38
+ return true;
39
+ }
40
+ return false;
41
+ }
@@ -0,0 +1,68 @@
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.createDoctorReport = createDoctorReport;
7
+ exports.formatDoctorReport = formatDoctorReport;
8
+ exports.handleDoctorCommand = handleDoctorCommand;
9
+ const node_fs_1 = __importDefault(require("node:fs"));
10
+ function createDoctorReport(paths, nodeVersion = process.version) {
11
+ const checks = [
12
+ pathCheck('docs', paths.projectDocsDir),
13
+ pathCheck('tasks', paths.projectTasksDir),
14
+ pathCheck('project-context', paths.projectHadaraDir)
15
+ ];
16
+ return {
17
+ schemaVersion: 'hadara.doctor.v1',
18
+ command: 'doctor',
19
+ ok: checks.every((check) => check.status === 'ok'),
20
+ runtime: {
21
+ node: nodeVersion
22
+ },
23
+ paths: {
24
+ portableRoot: paths.portableRoot,
25
+ dataRoot: paths.dataRoot,
26
+ projectRoot: paths.projectRoot
27
+ },
28
+ checks
29
+ };
30
+ }
31
+ function formatDoctorReport(report) {
32
+ return [
33
+ '[HADARA] Doctor',
34
+ `portableRoot: ${report.paths.portableRoot}`,
35
+ `dataRoot: ${report.paths.dataRoot}`,
36
+ `projectRoot: ${report.paths.projectRoot}`,
37
+ `Node: ${report.runtime.node}`,
38
+ ...report.checks.map((check) => `${formatCheckLabel(check.id)}${check.status}`)
39
+ ].join('\n');
40
+ }
41
+ function handleDoctorCommand(input) {
42
+ const report = createDoctorReport(input.paths);
43
+ if (input.jsonOutput) {
44
+ console.log(JSON.stringify(report, null, 2));
45
+ }
46
+ else {
47
+ console.log(formatDoctorReport(report));
48
+ }
49
+ if (!report.ok)
50
+ process.exitCode = 7;
51
+ return true;
52
+ }
53
+ function pathCheck(id, targetPath) {
54
+ return {
55
+ id,
56
+ status: node_fs_1.default.existsSync(targetPath) ? 'ok' : 'missing',
57
+ path: targetPath
58
+ };
59
+ }
60
+ function formatCheckLabel(id) {
61
+ if (id === 'docs')
62
+ return 'docs/: ';
63
+ if (id === 'tasks')
64
+ return 'tasks/: ';
65
+ if (id === 'project-context')
66
+ return '.hadara/: ';
67
+ return `${id}: `;
68
+ }