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,125 @@
|
|
|
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.createTaskListReport = createTaskListReport;
|
|
7
|
+
exports.createTaskShowReport = createTaskShowReport;
|
|
8
|
+
exports.createTaskReadReport = createTaskReadReport;
|
|
9
|
+
exports.formatTaskListReport = formatTaskListReport;
|
|
10
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
11
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
12
|
+
const task_capsule_1 = require("../task/task-capsule");
|
|
13
|
+
const evidence_list_1 = require("./evidence-list");
|
|
14
|
+
const TASK_CAPSULE_FILES = [
|
|
15
|
+
'TASK.md',
|
|
16
|
+
'PLAN.md',
|
|
17
|
+
'CONTEXT.md',
|
|
18
|
+
'ACCEPTANCE.md',
|
|
19
|
+
'FILES.md',
|
|
20
|
+
'TESTS.md',
|
|
21
|
+
'RISKS.md',
|
|
22
|
+
'DECISIONS.md',
|
|
23
|
+
'EVIDENCE.md',
|
|
24
|
+
'evidence.jsonl',
|
|
25
|
+
'HANDOFF.md'
|
|
26
|
+
];
|
|
27
|
+
function createTaskListReport(projectRoot) {
|
|
28
|
+
const tasks = (0, task_capsule_1.listTaskCapsules)(projectRoot).map((task) => summarizeTask(projectRoot, task));
|
|
29
|
+
return {
|
|
30
|
+
schemaVersion: 'hadara.task.list.v1',
|
|
31
|
+
command: 'task.list',
|
|
32
|
+
ok: true,
|
|
33
|
+
count: tasks.length,
|
|
34
|
+
tasks
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
function createTaskShowReport(projectRoot, taskId) {
|
|
38
|
+
const task = (0, task_capsule_1.listTaskCapsules)(projectRoot).find((item) => item.id === taskId);
|
|
39
|
+
if (!task) {
|
|
40
|
+
return {
|
|
41
|
+
schemaVersion: 'hadara.task.show.v1',
|
|
42
|
+
command: 'task.show',
|
|
43
|
+
ok: false,
|
|
44
|
+
issues: [
|
|
45
|
+
{
|
|
46
|
+
severity: 'error',
|
|
47
|
+
code: 'TASK_NOT_FOUND',
|
|
48
|
+
message: `Task Capsule not found: ${taskId}`
|
|
49
|
+
}
|
|
50
|
+
]
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
return {
|
|
54
|
+
schemaVersion: 'hadara.task.show.v1',
|
|
55
|
+
command: 'task.show',
|
|
56
|
+
ok: true,
|
|
57
|
+
task: {
|
|
58
|
+
...summarizeTask(projectRoot, task),
|
|
59
|
+
taskMarkdown: node_fs_1.default.readFileSync(node_path_1.default.join(task.dir, 'TASK.md'), 'utf8')
|
|
60
|
+
},
|
|
61
|
+
issues: []
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
function createTaskReadReport(projectRoot, taskId, options = {}) {
|
|
65
|
+
const task = (0, task_capsule_1.listTaskCapsules)(projectRoot).find((item) => item.id === taskId);
|
|
66
|
+
if (!task) {
|
|
67
|
+
return {
|
|
68
|
+
schemaVersion: 'hadara.task.read.v1',
|
|
69
|
+
command: 'task.read',
|
|
70
|
+
ok: false,
|
|
71
|
+
issues: [
|
|
72
|
+
{
|
|
73
|
+
severity: 'error',
|
|
74
|
+
code: 'TASK_NOT_FOUND',
|
|
75
|
+
message: `Task Capsule not found: ${taskId}`
|
|
76
|
+
}
|
|
77
|
+
]
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
const files = Object.fromEntries(TASK_CAPSULE_FILES.map((fileName) => {
|
|
81
|
+
const filePath = node_path_1.default.join(task.dir, fileName);
|
|
82
|
+
return [fileName, node_fs_1.default.existsSync(filePath) ? node_fs_1.default.readFileSync(filePath, 'utf8') : ''];
|
|
83
|
+
}));
|
|
84
|
+
const evidenceParse = (0, evidence_list_1.parseEvidenceIndexFile)(node_path_1.default.join(task.dir, 'evidence.jsonl'), task.id);
|
|
85
|
+
const includePrivate = options.includePrivate === true;
|
|
86
|
+
const evidenceRecords = evidenceParse.records.filter((record) => includePrivate || record.visibility !== 'private');
|
|
87
|
+
files['evidence.jsonl'] = formatEvidenceIndexFile(evidenceRecords);
|
|
88
|
+
return {
|
|
89
|
+
schemaVersion: 'hadara.task.read.v1',
|
|
90
|
+
command: 'task.read',
|
|
91
|
+
ok: !evidenceParse.issues.some((issue) => issue.severity === 'error'),
|
|
92
|
+
task: summarizeTask(projectRoot, task),
|
|
93
|
+
files,
|
|
94
|
+
evidenceIndex: evidenceRecords,
|
|
95
|
+
issues: evidenceParse.issues
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
function formatTaskListReport(report) {
|
|
99
|
+
return report.tasks.map((task) => `${task.id}\t${task.title}\t${task.capsule}`).join('\n');
|
|
100
|
+
}
|
|
101
|
+
function formatEvidenceIndexFile(records) {
|
|
102
|
+
if (records.length === 0)
|
|
103
|
+
return '';
|
|
104
|
+
return `${records.map((record) => JSON.stringify(record)).join('\n')}\n`;
|
|
105
|
+
}
|
|
106
|
+
function summarizeTask(projectRoot, task) {
|
|
107
|
+
return {
|
|
108
|
+
id: task.id,
|
|
109
|
+
title: task.title,
|
|
110
|
+
status: readTaskStatus(task),
|
|
111
|
+
slug: task.slug,
|
|
112
|
+
capsule: toPortablePath(node_path_1.default.relative(projectRoot, task.dir))
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
function readTaskStatus(task) {
|
|
116
|
+
const taskPath = node_path_1.default.join(task.dir, 'TASK.md');
|
|
117
|
+
if (!node_fs_1.default.existsSync(taskPath))
|
|
118
|
+
return 'Unknown';
|
|
119
|
+
const content = node_fs_1.default.readFileSync(taskPath, 'utf8');
|
|
120
|
+
const match = content.match(/^## Status\s*\n+([\s\S]*?)(?:\n## |\s*$)/m);
|
|
121
|
+
return match?.[1]?.trim().split(/\r?\n/)[0]?.trim() || 'Unknown';
|
|
122
|
+
}
|
|
123
|
+
function toPortablePath(value) {
|
|
124
|
+
return value.split(node_path_1.default.sep).join('/');
|
|
125
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createToolsListReport = createToolsListReport;
|
|
4
|
+
const capability_registry_1 = require("./capability-registry");
|
|
5
|
+
function createToolsListReport(options = {}) {
|
|
6
|
+
return {
|
|
7
|
+
schemaVersion: 'hadara.tools.list.v1',
|
|
8
|
+
command: 'tools.list',
|
|
9
|
+
ok: true,
|
|
10
|
+
surfaces: {
|
|
11
|
+
cli: capability_registry_1.HADARA_CLI_CAPABILITIES.map((surface) => ({ ...surface })),
|
|
12
|
+
mcp: createMcpSurfaces(options)
|
|
13
|
+
},
|
|
14
|
+
disabled: capability_registry_1.HADARA_DISABLED_CAPABILITIES.map((surface) => ({ ...surface })),
|
|
15
|
+
issues: []
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
function createMcpSurfaces(options) {
|
|
19
|
+
const readTools = capability_registry_1.HADARA_MCP_READ_CAPABILITIES.map((capability) => ({ ...capability.surface }));
|
|
20
|
+
const evidenceAttach = {
|
|
21
|
+
...capability_registry_1.HADARA_MCP_EVIDENCE_ATTACH_CAPABILITY.surface,
|
|
22
|
+
enabledByDefault: options.enableEvidenceAttach === true,
|
|
23
|
+
availability: options.enableEvidenceAttach === true ? 'default' : capability_registry_1.HADARA_MCP_EVIDENCE_ATTACH_CAPABILITY.surface.availability
|
|
24
|
+
};
|
|
25
|
+
return [...readTools, evidenceAttach];
|
|
26
|
+
}
|
|
@@ -0,0 +1,240 @@
|
|
|
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.createWritePreflightReport = createWritePreflightReport;
|
|
7
|
+
exports.assertWritePreflightSchema = assertWritePreflightSchema;
|
|
8
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
9
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
10
|
+
const schema_1 = require("../core/schema");
|
|
11
|
+
const fs_1 = require("../core/fs");
|
|
12
|
+
const TASK_FILE_NAMES = [
|
|
13
|
+
'TASK.md',
|
|
14
|
+
'PLAN.md',
|
|
15
|
+
'CONTEXT.md',
|
|
16
|
+
'FILES.md',
|
|
17
|
+
'ACCEPTANCE.md',
|
|
18
|
+
'TESTS.md',
|
|
19
|
+
'RISKS.md',
|
|
20
|
+
'DECISIONS.md',
|
|
21
|
+
'EVIDENCE.md',
|
|
22
|
+
'evidence.jsonl',
|
|
23
|
+
'HANDOFF.md'
|
|
24
|
+
];
|
|
25
|
+
function createWritePreflightReport(projectRoot, targetArgs) {
|
|
26
|
+
const args = normalizeTargetArgs(targetArgs);
|
|
27
|
+
const command = identifyCommand(args);
|
|
28
|
+
let report;
|
|
29
|
+
switch (command) {
|
|
30
|
+
case 'task.create':
|
|
31
|
+
report = taskCreateReport(projectRoot, args);
|
|
32
|
+
break;
|
|
33
|
+
case 'evidence.collect':
|
|
34
|
+
report = evidenceCollectReport(projectRoot, args);
|
|
35
|
+
break;
|
|
36
|
+
case 'handoff.update':
|
|
37
|
+
report = simpleProjectWriteReport('handoff.update', ['docs/AGENT_HANDOFF.md']);
|
|
38
|
+
break;
|
|
39
|
+
case 'run-state.start':
|
|
40
|
+
case 'run-state.update':
|
|
41
|
+
case 'run-state.complete':
|
|
42
|
+
report = simpleProjectWriteReport(command, ['.hadara/local/state/active-run.json'], [
|
|
43
|
+
{
|
|
44
|
+
severity: 'warning',
|
|
45
|
+
code: 'WRITE_COMMAND_DEFERRED',
|
|
46
|
+
message: `${command} is a planned CLI-owned write boundary; the mutation command is not implemented yet.`
|
|
47
|
+
}
|
|
48
|
+
]);
|
|
49
|
+
break;
|
|
50
|
+
case 'debt.add':
|
|
51
|
+
case 'debt.update':
|
|
52
|
+
report = {
|
|
53
|
+
...simpleProjectWriteReport(command, ['docs/OPERATIONAL_DEBT.md'], [
|
|
54
|
+
{
|
|
55
|
+
severity: 'warning',
|
|
56
|
+
code: 'DEBT_WRITE_STORE_DEFERRED',
|
|
57
|
+
message: 'Operational debt mutation is deferred; the durable write store is not implemented yet.'
|
|
58
|
+
}
|
|
59
|
+
]),
|
|
60
|
+
risk: 'medium',
|
|
61
|
+
requiresApproval: true
|
|
62
|
+
};
|
|
63
|
+
break;
|
|
64
|
+
default:
|
|
65
|
+
report = {
|
|
66
|
+
schemaVersion: 'hadara.write.preflight.v1',
|
|
67
|
+
ok: false,
|
|
68
|
+
command: 'unknown',
|
|
69
|
+
risk: 'low',
|
|
70
|
+
requiresApproval: false,
|
|
71
|
+
workspaceBoundary: 'project',
|
|
72
|
+
writes: [],
|
|
73
|
+
issues: [
|
|
74
|
+
{
|
|
75
|
+
severity: 'error',
|
|
76
|
+
code: 'UNSUPPORTED_WRITE_COMMAND',
|
|
77
|
+
message: `Unsupported write preflight target: ${args.join(' ') || '(empty)'}`
|
|
78
|
+
}
|
|
79
|
+
]
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
assertWritePreflightSchema(report);
|
|
83
|
+
return report;
|
|
84
|
+
}
|
|
85
|
+
function assertWritePreflightSchema(report) {
|
|
86
|
+
(0, schema_1.assertSchema)('hadara.write.preflight.v1', report);
|
|
87
|
+
}
|
|
88
|
+
function taskCreateReport(projectRoot, args) {
|
|
89
|
+
const title = extractValueAfterPrefix(args, ['task', 'create']);
|
|
90
|
+
const issues = [];
|
|
91
|
+
const taskId = nextTaskIdForPreflight(node_path_1.default.join(projectRoot, 'tasks'));
|
|
92
|
+
const slug = (0, fs_1.slugify)(title || 'task');
|
|
93
|
+
const capsule = `tasks/${taskId}-${slug}`;
|
|
94
|
+
if (!title) {
|
|
95
|
+
issues.push({
|
|
96
|
+
severity: 'error',
|
|
97
|
+
code: 'TASK_TITLE_REQUIRED',
|
|
98
|
+
message: 'task create preflight requires a title.'
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
return {
|
|
102
|
+
schemaVersion: 'hadara.write.preflight.v1',
|
|
103
|
+
ok: issues.every((issue) => issue.severity !== 'error'),
|
|
104
|
+
command: 'task.create',
|
|
105
|
+
risk: 'low',
|
|
106
|
+
requiresApproval: false,
|
|
107
|
+
workspaceBoundary: 'project',
|
|
108
|
+
writes: [...TASK_FILE_NAMES.map((fileName) => `${capsule}/${fileName}`), 'docs/TASK_BOARD.md'],
|
|
109
|
+
issues
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
function evidenceCollectReport(projectRoot, args) {
|
|
113
|
+
const taskId = getOptionValue(args, '--task');
|
|
114
|
+
const kind = getOptionValue(args, '--kind') ?? 'note';
|
|
115
|
+
const artifactPath = getOptionValue(args, '--path');
|
|
116
|
+
const visibility = args.includes('--private') ? 'private' : getOptionValue(args, '--visibility') ?? 'public';
|
|
117
|
+
const issues = [];
|
|
118
|
+
if (!taskId) {
|
|
119
|
+
issues.push({
|
|
120
|
+
severity: 'error',
|
|
121
|
+
code: 'TASK_ID_REQUIRED',
|
|
122
|
+
message: 'evidence collect preflight requires --task <task-id>.'
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
const capsule = taskId ? findTaskCapsulePath(projectRoot, taskId) : null;
|
|
126
|
+
if (taskId && !capsule) {
|
|
127
|
+
issues.push({
|
|
128
|
+
severity: 'error',
|
|
129
|
+
code: 'TASK_CAPSULE_NOT_FOUND',
|
|
130
|
+
message: `Task Capsule not found: ${taskId}`
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
const writes = capsule ? [`${capsule}/EVIDENCE.md`, `${capsule}/evidence.jsonl`] : [];
|
|
134
|
+
if (capsule && artifactPath && visibility === 'public') {
|
|
135
|
+
writes.push(`${capsule}/artifacts/${kind}/<timestamp>-${safeFilePart(node_path_1.default.basename(artifactPath))}`);
|
|
136
|
+
}
|
|
137
|
+
if (taskId && artifactPath && visibility === 'private') {
|
|
138
|
+
writes.push(`.hadara/local/portable/data/private-evidence/${taskId}/<evidence-id>.bin`);
|
|
139
|
+
writes.push(`.hadara/local/portable/data/private-evidence/${taskId}/manifest.jsonl`);
|
|
140
|
+
writes.push('.hadara/local/portable/data/audit/audit.jsonl');
|
|
141
|
+
}
|
|
142
|
+
return {
|
|
143
|
+
schemaVersion: 'hadara.write.preflight.v1',
|
|
144
|
+
ok: issues.every((issue) => issue.severity !== 'error'),
|
|
145
|
+
command: 'evidence.collect',
|
|
146
|
+
risk: visibility === 'private' ? 'medium' : 'low',
|
|
147
|
+
requiresApproval: visibility === 'private',
|
|
148
|
+
workspaceBoundary: visibility === 'private' ? 'project+private-portable' : 'project',
|
|
149
|
+
writes,
|
|
150
|
+
issues
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
function simpleProjectWriteReport(command, writes, issues = []) {
|
|
154
|
+
return {
|
|
155
|
+
schemaVersion: 'hadara.write.preflight.v1',
|
|
156
|
+
ok: issues.every((issue) => issue.severity !== 'error'),
|
|
157
|
+
command,
|
|
158
|
+
risk: 'low',
|
|
159
|
+
requiresApproval: false,
|
|
160
|
+
workspaceBoundary: 'project',
|
|
161
|
+
writes,
|
|
162
|
+
issues
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
function identifyCommand(args) {
|
|
166
|
+
const [root, sub] = args;
|
|
167
|
+
if (root === 'task' && sub === 'create')
|
|
168
|
+
return 'task.create';
|
|
169
|
+
if (root === 'evidence' && sub === 'collect')
|
|
170
|
+
return 'evidence.collect';
|
|
171
|
+
if (root === 'handoff' && sub === 'update')
|
|
172
|
+
return 'handoff.update';
|
|
173
|
+
if (root === 'run-state' && (sub === 'start' || sub === 'update' || sub === 'complete'))
|
|
174
|
+
return `run-state.${sub}`;
|
|
175
|
+
if (root === 'debt' && (sub === 'add' || sub === 'update'))
|
|
176
|
+
return `debt.${sub}`;
|
|
177
|
+
return 'unknown';
|
|
178
|
+
}
|
|
179
|
+
function normalizeTargetArgs(args) {
|
|
180
|
+
const normalized = [];
|
|
181
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
182
|
+
const value = args[index];
|
|
183
|
+
if (value === '--')
|
|
184
|
+
continue;
|
|
185
|
+
if (value === '--json')
|
|
186
|
+
continue;
|
|
187
|
+
if (value === '--project') {
|
|
188
|
+
index += 1;
|
|
189
|
+
continue;
|
|
190
|
+
}
|
|
191
|
+
normalized.push(value);
|
|
192
|
+
}
|
|
193
|
+
return normalized;
|
|
194
|
+
}
|
|
195
|
+
function extractValueAfterPrefix(args, prefix) {
|
|
196
|
+
return args
|
|
197
|
+
.slice(prefix.length)
|
|
198
|
+
.filter((value, index, values) => {
|
|
199
|
+
if (value.startsWith('--'))
|
|
200
|
+
return false;
|
|
201
|
+
const previous = values[index - 1];
|
|
202
|
+
return previous !== '--project';
|
|
203
|
+
})
|
|
204
|
+
.join(' ')
|
|
205
|
+
.trim();
|
|
206
|
+
}
|
|
207
|
+
function getOptionValue(args, option) {
|
|
208
|
+
const index = args.indexOf(option);
|
|
209
|
+
if (index === -1)
|
|
210
|
+
return undefined;
|
|
211
|
+
const value = args[index + 1];
|
|
212
|
+
if (!value || value.startsWith('--'))
|
|
213
|
+
return undefined;
|
|
214
|
+
return value;
|
|
215
|
+
}
|
|
216
|
+
function nextTaskIdForPreflight(tasksDir) {
|
|
217
|
+
if (!node_fs_1.default.existsSync(tasksDir))
|
|
218
|
+
return 'T-0001';
|
|
219
|
+
const max = node_fs_1.default
|
|
220
|
+
.readdirSync(tasksDir, { withFileTypes: true })
|
|
221
|
+
.filter((entry) => entry.isDirectory())
|
|
222
|
+
.map((entry) => entry.name.match(/^T-(\d{4})-/)?.[1])
|
|
223
|
+
.filter((value) => Boolean(value))
|
|
224
|
+
.map(Number)
|
|
225
|
+
.reduce((acc, value) => Math.max(acc, value), 0);
|
|
226
|
+
return `T-${String(max + 1).padStart(4, '0')}`;
|
|
227
|
+
}
|
|
228
|
+
function findTaskCapsulePath(projectRoot, taskId) {
|
|
229
|
+
const tasksDir = node_path_1.default.join(projectRoot, 'tasks');
|
|
230
|
+
if (!node_fs_1.default.existsSync(tasksDir))
|
|
231
|
+
return null;
|
|
232
|
+
const entry = node_fs_1.default.readdirSync(tasksDir).find((name) => name.startsWith(`${taskId}-`));
|
|
233
|
+
return entry ? toPortablePath(node_path_1.default.relative(projectRoot, node_path_1.default.join(tasksDir, entry))) : null;
|
|
234
|
+
}
|
|
235
|
+
function safeFilePart(value) {
|
|
236
|
+
return value.replace(/[^A-Za-z0-9._-]+/g, '-').replace(/^-+|-+$/g, '') || 'artifact';
|
|
237
|
+
}
|
|
238
|
+
function toPortablePath(value) {
|
|
239
|
+
return value.split(node_path_1.default.sep).join('/');
|
|
240
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
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.TASK_FILES = void 0;
|
|
7
|
+
exports.isTaskCapsuleScaffoldContent = isTaskCapsuleScaffoldContent;
|
|
8
|
+
exports.nextTaskId = nextTaskId;
|
|
9
|
+
exports.createTaskCapsule = createTaskCapsule;
|
|
10
|
+
exports.listTaskCapsules = listTaskCapsules;
|
|
11
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
12
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
13
|
+
const fs_1 = require("../core/fs");
|
|
14
|
+
exports.TASK_FILES = {
|
|
15
|
+
'TASK.md': (task) => `# ${task.id} ${task.title}\n\n## Goal\n\nTBD.\n\n## Scope\n\nTBD.\n\n## Out of Scope\n\nTBD.\n\n## Status\n\nDraft\n`,
|
|
16
|
+
'PLAN.md': () => `# Plan\n\n1. Read relevant docs.\n2. Implement the smallest useful slice.\n3. Run tests.\n4. Attach evidence.\n5. Update handoff.\n`,
|
|
17
|
+
'CONTEXT.md': () => `# Context\n\nRelevant documents, files, assumptions, and constraints.\n`,
|
|
18
|
+
'FILES.md': () => `# Files\n\n| Path | Action | Reason |\n|---|---|---|\n`,
|
|
19
|
+
'ACCEPTANCE.md': () => `# Acceptance Criteria\n\n- [ ] Scope is implemented.\n- [ ] Tests or explicit constraints are recorded.\n- [ ] Evidence is attached.\n- [ ] Handoff is updated.\n`,
|
|
20
|
+
'TESTS.md': () => `# Tests\n\n## Required\n\n- npm test\n\n## Optional\n\n- npm run check\n`,
|
|
21
|
+
'RISKS.md': () => `# Risks\n\n| Risk | Mitigation |\n|---|---|\n`,
|
|
22
|
+
'DECISIONS.md': () => `# Decisions\n\nRecord task-local design decisions here.\n`,
|
|
23
|
+
'EVIDENCE.md': () => `# Evidence\n\n| Time | Kind | Summary | Result |\n|---|---|---|---|\n`,
|
|
24
|
+
'evidence.jsonl': () => '',
|
|
25
|
+
'HANDOFF.md': () => `# Handoff\n\n## Last Completed\n\nTBD.\n\n## Next Recommended Step\n\nTBD.\n`
|
|
26
|
+
};
|
|
27
|
+
function isTaskCapsuleScaffoldContent(task, fileName, content) {
|
|
28
|
+
if (fileName === 'TASK.md') {
|
|
29
|
+
return ['## Goal', '## Scope', '## Out of Scope'].some((heading) => isPlaceholderSection(readMarkdownSection(content, heading)));
|
|
30
|
+
}
|
|
31
|
+
if (fileName === 'ACCEPTANCE.md') {
|
|
32
|
+
return acceptanceChecklistText(content).join('\n') === [
|
|
33
|
+
'Scope is implemented.',
|
|
34
|
+
'Tests or explicit constraints are recorded.',
|
|
35
|
+
'Evidence is attached.',
|
|
36
|
+
'Handoff is updated.'
|
|
37
|
+
].join('\n');
|
|
38
|
+
}
|
|
39
|
+
const factory = exports.TASK_FILES[fileName];
|
|
40
|
+
if (!factory)
|
|
41
|
+
return false;
|
|
42
|
+
return normalizeMarkdown(content) === normalizeMarkdown(factory(task));
|
|
43
|
+
}
|
|
44
|
+
function nextTaskId(tasksDir) {
|
|
45
|
+
(0, fs_1.ensureDir)(tasksDir);
|
|
46
|
+
const max = node_fs_1.default
|
|
47
|
+
.readdirSync(tasksDir, { withFileTypes: true })
|
|
48
|
+
.filter((entry) => entry.isDirectory())
|
|
49
|
+
.map((entry) => entry.name.match(/^T-(\d{4})-/)?.[1])
|
|
50
|
+
.filter((value) => Boolean(value))
|
|
51
|
+
.map(Number)
|
|
52
|
+
.reduce((acc, value) => Math.max(acc, value), 0);
|
|
53
|
+
return `T-${String(max + 1).padStart(4, '0')}`;
|
|
54
|
+
}
|
|
55
|
+
function createTaskCapsule(projectRoot, title) {
|
|
56
|
+
const tasksDir = node_path_1.default.join(projectRoot, 'tasks');
|
|
57
|
+
const id = nextTaskId(tasksDir);
|
|
58
|
+
const slug = (0, fs_1.slugify)(title);
|
|
59
|
+
const dir = node_path_1.default.join(tasksDir, `${id}-${slug}`);
|
|
60
|
+
const task = { id, title, slug, dir };
|
|
61
|
+
(0, fs_1.ensureDir)(dir);
|
|
62
|
+
for (const [fileName, factory] of Object.entries(exports.TASK_FILES)) {
|
|
63
|
+
(0, fs_1.writeFileIfMissing)(node_path_1.default.join(dir, fileName), factory(task));
|
|
64
|
+
}
|
|
65
|
+
const taskBoard = node_path_1.default.join(projectRoot, 'docs', 'TASK_BOARD.md');
|
|
66
|
+
(0, fs_1.ensureDir)(node_path_1.default.dirname(taskBoard));
|
|
67
|
+
const line = `| ${id} | ${title.replace(/\|/g, '/')} | Draft | ${node_path_1.default.relative(projectRoot, dir)} | |\n`;
|
|
68
|
+
if (!node_fs_1.default.existsSync(taskBoard)) {
|
|
69
|
+
node_fs_1.default.writeFileSync(taskBoard, `# TASK_BOARD\n\n| ID | Title | Status | Capsule | Notes |\n|---|---|---|---|---|\n${line}`, 'utf8');
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
const current = node_fs_1.default.readFileSync(taskBoard, 'utf8');
|
|
73
|
+
if (!current.includes(`| ${id} |`)) {
|
|
74
|
+
node_fs_1.default.appendFileSync(taskBoard, line, 'utf8');
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return task;
|
|
78
|
+
}
|
|
79
|
+
function listTaskCapsules(projectRoot) {
|
|
80
|
+
const tasksDir = node_path_1.default.join(projectRoot, 'tasks');
|
|
81
|
+
if (!node_fs_1.default.existsSync(tasksDir))
|
|
82
|
+
return [];
|
|
83
|
+
return node_fs_1.default
|
|
84
|
+
.readdirSync(tasksDir, { withFileTypes: true })
|
|
85
|
+
.filter((entry) => entry.isDirectory() && /^T-\d{4}-/.test(entry.name))
|
|
86
|
+
.map((entry) => {
|
|
87
|
+
const [id, ...slugParts] = entry.name.split('-');
|
|
88
|
+
const number = slugParts.shift();
|
|
89
|
+
const fullId = `${id}-${number}`;
|
|
90
|
+
const slug = slugParts.join('-');
|
|
91
|
+
const dir = node_path_1.default.join(tasksDir, entry.name);
|
|
92
|
+
const taskMd = node_path_1.default.join(dir, 'TASK.md');
|
|
93
|
+
const title = node_fs_1.default.existsSync(taskMd)
|
|
94
|
+
? node_fs_1.default.readFileSync(taskMd, 'utf8').split('\n')[0].replace(/^#\s*T-\d{4}\s*/, '').trim()
|
|
95
|
+
: slug;
|
|
96
|
+
return { id: fullId, title, slug, dir };
|
|
97
|
+
})
|
|
98
|
+
.sort((a, b) => a.id.localeCompare(b.id));
|
|
99
|
+
}
|
|
100
|
+
function readMarkdownSection(content, heading) {
|
|
101
|
+
const start = content.indexOf(heading);
|
|
102
|
+
if (start < 0)
|
|
103
|
+
return '';
|
|
104
|
+
const afterHeading = content.slice(start + heading.length);
|
|
105
|
+
const nextHeading = afterHeading.search(/\n##\s+/);
|
|
106
|
+
return nextHeading >= 0 ? afterHeading.slice(0, nextHeading) : afterHeading;
|
|
107
|
+
}
|
|
108
|
+
function isPlaceholderSection(value) {
|
|
109
|
+
const normalized = value.trim();
|
|
110
|
+
return normalized.length === 0 || /^TBD\.?$/i.test(normalized);
|
|
111
|
+
}
|
|
112
|
+
function normalizeMarkdown(value) {
|
|
113
|
+
return value.replace(/\r\n/g, '\n').trim();
|
|
114
|
+
}
|
|
115
|
+
function acceptanceChecklistText(content) {
|
|
116
|
+
return content
|
|
117
|
+
.split(/\r?\n/)
|
|
118
|
+
.map((line) => line.trim())
|
|
119
|
+
.filter((line) => /^-\s+\[[ xX]\]/.test(line))
|
|
120
|
+
.map((line) => line.replace(/^-\s+\[[ xX]\]\s*/, '').trim());
|
|
121
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.runFakeShellCommand = runFakeShellCommand;
|
|
4
|
+
const policy_1 = require("../policy/policy");
|
|
5
|
+
const preflight_1 = require("../policy/preflight");
|
|
6
|
+
function runFakeShellCommand(input) {
|
|
7
|
+
const mode = (0, policy_1.parsePermissionMode)(input.mode);
|
|
8
|
+
const preflight = (0, preflight_1.createShellExecutionPreflight)(input.command, mode);
|
|
9
|
+
if (preflight.execution.status === 'denied') {
|
|
10
|
+
return toObservation(input.command, mode, preflight, {
|
|
11
|
+
status: 'policy_denied',
|
|
12
|
+
exitCode: preflight.execution.exitCodeIfBlocked ?? 2,
|
|
13
|
+
stdout: '',
|
|
14
|
+
stderr: preflight.decision.reason,
|
|
15
|
+
reason: preflight.decision.reason
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
if (preflight.execution.status === 'requires_approval') {
|
|
19
|
+
return toObservation(input.command, mode, preflight, {
|
|
20
|
+
status: 'requires_approval',
|
|
21
|
+
exitCode: 0,
|
|
22
|
+
stdout: '',
|
|
23
|
+
stderr: '',
|
|
24
|
+
reason: preflight.decision.reason
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
const result = input.fixtures[input.command];
|
|
28
|
+
if (!result) {
|
|
29
|
+
return toObservation(input.command, mode, preflight, {
|
|
30
|
+
status: 'not_configured',
|
|
31
|
+
exitCode: 127,
|
|
32
|
+
stdout: '',
|
|
33
|
+
stderr: `No fake shell fixture configured for command: ${input.command}`,
|
|
34
|
+
reason: 'Fake shell fixtures must explicitly define allowed command outputs.'
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
return toObservation(input.command, mode, preflight, {
|
|
38
|
+
status: 'completed',
|
|
39
|
+
exitCode: result.exitCode,
|
|
40
|
+
stdout: result.stdout ?? '',
|
|
41
|
+
stderr: result.stderr ?? ''
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
function toObservation(commandText, mode, preflight, result) {
|
|
45
|
+
return {
|
|
46
|
+
schemaVersion: 'hadara.tools.fake-shell.v1',
|
|
47
|
+
command: 'tools.fake-shell.run',
|
|
48
|
+
ok: result.status === 'completed' && result.exitCode === 0,
|
|
49
|
+
input: {
|
|
50
|
+
mode,
|
|
51
|
+
command: commandText
|
|
52
|
+
},
|
|
53
|
+
preflight,
|
|
54
|
+
result
|
|
55
|
+
};
|
|
56
|
+
}
|