hadara 0.3.3 → 0.3.4-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/README.md +17 -9
- package/dist/cli/dashboard.js +13 -6
- package/dist/cli/evidence.js +42 -0
- package/dist/cli/handoff.js +13 -0
- package/dist/cli/init.js +20 -2
- package/dist/cli/main.js +3 -0
- package/dist/cli/package-smoke.js +33 -1
- package/dist/cli/release-closeout.js +22 -0
- package/dist/context/context-pack.js +91 -7
- package/dist/context/session-start.js +39 -0
- package/dist/core/schema.js +8 -0
- package/dist/handoff/handoff-stale-problems.js +150 -0
- package/dist/schemas/context-pack.schema.json +23 -0
- package/dist/schemas/evidence-summary.schema.json +73 -0
- package/dist/schemas/handoff-stale-problems.schema.json +64 -0
- package/dist/schemas/package-recycle.schema.json +150 -0
- package/dist/schemas/release-closeout.schema.json +39 -0
- package/dist/schemas/schema-index.json +28 -0
- package/dist/schemas/session-start.schema.json +30 -0
- package/dist/schemas/smoke-evidence-summary.schema.json +1 -1
- package/dist/services/capability-registry.js +115 -0
- package/dist/services/dashboard-bootstrap.js +3 -3
- package/dist/services/evidence-summary.js +66 -0
- package/dist/services/package-recycle.js +709 -0
- package/dist/services/release-closeout.js +136 -0
- package/dist/task/task-finalize.js +36 -5
- package/dist/task/task-lifecycle.js +6 -6
- package/package.json +1 -1
|
@@ -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.createReleaseCloseoutReport = createReleaseCloseoutReport;
|
|
7
|
+
exports.formatReleaseCloseoutReport = formatReleaseCloseoutReport;
|
|
8
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
9
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
10
|
+
const task_capsule_1 = require("../task/task-capsule");
|
|
11
|
+
const SHARED_FILES = [
|
|
12
|
+
{ path: 'docs/RELEASE_READINESS.md', kind: 'release-readiness', role: 'source-readiness' },
|
|
13
|
+
{ path: 'docs/RELEASE_NOTES.md', kind: 'release-notes', role: 'publish' },
|
|
14
|
+
{ path: 'docs/PROJECT_STATE.md', kind: 'project-state', role: 'shared-state' },
|
|
15
|
+
{ path: 'docs/AGENT_HANDOFF.md', kind: 'agent-handoff', role: 'shared-state' },
|
|
16
|
+
{ path: 'docs/TASK_BOARD.md', kind: 'task-board', role: 'shared-state' },
|
|
17
|
+
{ path: 'docs/DEVELOPMENT_SLICES.md', kind: 'development-slices', role: 'shared-state' }
|
|
18
|
+
];
|
|
19
|
+
const CAPSULE_FILES = ['TASK.md', 'ACCEPTANCE.md', 'TESTS.md', 'EVIDENCE.md', 'HANDOFF.md'];
|
|
20
|
+
function createReleaseCloseoutReport(projectRoot, input) {
|
|
21
|
+
const version = input.version ?? null;
|
|
22
|
+
const taskId = input.taskId ?? null;
|
|
23
|
+
const issues = [];
|
|
24
|
+
if (!version)
|
|
25
|
+
issues.push({ severity: 'error', code: 'RELEASE_CLOSEOUT_VERSION_REQUIRED', message: 'release closeout requires --version <version>.' });
|
|
26
|
+
if (!taskId)
|
|
27
|
+
issues.push({ severity: 'error', code: 'RELEASE_CLOSEOUT_TASK_REQUIRED', message: 'release closeout requires --task <task-id>.' });
|
|
28
|
+
const task = taskId ? (0, task_capsule_1.listTaskCapsules)(projectRoot).find((candidate) => candidate.id === taskId) : undefined;
|
|
29
|
+
if (taskId && !task)
|
|
30
|
+
issues.push({ severity: 'warning', code: 'RELEASE_CLOSEOUT_TASK_NOT_FOUND', message: `Task Capsule not found: ${taskId}.`, path: `tasks/${taskId}` });
|
|
31
|
+
const surfaces = [
|
|
32
|
+
...SHARED_FILES.map((surface) => analyzeSharedSurface(projectRoot, surface, version, taskId)),
|
|
33
|
+
...CAPSULE_FILES.map((fileName) => analyzeCapsuleSurface(projectRoot, task?.dir, fileName, version, taskId))
|
|
34
|
+
];
|
|
35
|
+
const suggestedFragments = createSuggestedFragments(version, taskId);
|
|
36
|
+
const current = surfaces.filter((surface) => surface.status === 'current').length;
|
|
37
|
+
const stale = surfaces.filter((surface) => surface.status === 'stale').length;
|
|
38
|
+
const missing = surfaces.filter((surface) => surface.status === 'missing').length;
|
|
39
|
+
return {
|
|
40
|
+
schemaVersion: 'hadara.releaseCloseout.v1',
|
|
41
|
+
command: 'release.closeout',
|
|
42
|
+
ok: !issues.some((issue) => issue.severity === 'error'),
|
|
43
|
+
readOnly: true,
|
|
44
|
+
generatedAt: new Date().toISOString(),
|
|
45
|
+
input: { version, taskId },
|
|
46
|
+
summary: {
|
|
47
|
+
files: surfaces.length,
|
|
48
|
+
current,
|
|
49
|
+
stale,
|
|
50
|
+
missing,
|
|
51
|
+
suggestedFragments: suggestedFragments.length
|
|
52
|
+
},
|
|
53
|
+
surfaces,
|
|
54
|
+
suggestedFragments,
|
|
55
|
+
issues
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
function formatReleaseCloseoutReport(report) {
|
|
59
|
+
const lines = [`[HADARA] release closeout ${report.input.version ?? '(missing-version)'}: ${report.ok ? 'ok' : 'issues'}`];
|
|
60
|
+
lines.push(`files=${report.summary.files} current=${report.summary.current} stale=${report.summary.stale} missing=${report.summary.missing}`);
|
|
61
|
+
for (const surface of report.surfaces)
|
|
62
|
+
lines.push(`${surface.status}\t${surface.path}\t${surface.summary}`);
|
|
63
|
+
for (const issue of report.issues)
|
|
64
|
+
lines.push(`[${issue.severity}] ${issue.code}: ${issue.message}`);
|
|
65
|
+
return lines.join('\n');
|
|
66
|
+
}
|
|
67
|
+
function analyzeSharedSurface(projectRoot, surface, version, taskId) {
|
|
68
|
+
const absolutePath = node_path_1.default.join(projectRoot, surface.path);
|
|
69
|
+
const content = node_fs_1.default.existsSync(absolutePath) ? node_fs_1.default.readFileSync(absolutePath, 'utf8') : '';
|
|
70
|
+
const expectedSignals = expectedSharedSignals(surface.kind, version, taskId);
|
|
71
|
+
return buildSurface(surface, content, expectedSignals);
|
|
72
|
+
}
|
|
73
|
+
function analyzeCapsuleSurface(projectRoot, taskDir, fileName, version, taskId) {
|
|
74
|
+
const relativePath = taskDir ? toPortablePath(node_path_1.default.relative(projectRoot, node_path_1.default.join(taskDir, fileName))) : `tasks/${taskId ?? 'T-XXXX'}/${fileName}`;
|
|
75
|
+
const absolutePath = taskDir ? node_path_1.default.join(taskDir, fileName) : '';
|
|
76
|
+
const content = absolutePath && node_fs_1.default.existsSync(absolutePath) ? node_fs_1.default.readFileSync(absolutePath, 'utf8') : '';
|
|
77
|
+
return buildSurface({
|
|
78
|
+
path: relativePath,
|
|
79
|
+
kind: 'task-capsule',
|
|
80
|
+
role: 'capsule'
|
|
81
|
+
}, content, [taskId, version].filter((value) => Boolean(value)));
|
|
82
|
+
}
|
|
83
|
+
function buildSurface(surface, content, expectedSignals) {
|
|
84
|
+
const matchedSignals = expectedSignals.filter((signal) => content.includes(signal));
|
|
85
|
+
const missingSignals = expectedSignals.filter((signal) => !content.includes(signal));
|
|
86
|
+
const status = !content ? 'missing' : missingSignals.length === 0 ? 'current' : 'stale';
|
|
87
|
+
return {
|
|
88
|
+
...surface,
|
|
89
|
+
status,
|
|
90
|
+
expectedSignals,
|
|
91
|
+
matchedSignals,
|
|
92
|
+
missingSignals,
|
|
93
|
+
summary: status === 'current' ? 'Expected closeout signals are present.' : status === 'missing' ? 'File is missing or task capsule was not found.' : `Missing closeout signals: ${missingSignals.join(', ')}.`
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
function expectedSharedSignals(kind, version, taskId) {
|
|
97
|
+
const signals = [version, taskId].filter((value) => Boolean(value));
|
|
98
|
+
if (kind === 'release-readiness')
|
|
99
|
+
return signals;
|
|
100
|
+
if (kind === 'release-notes')
|
|
101
|
+
return [version].filter((value) => Boolean(value));
|
|
102
|
+
return signals;
|
|
103
|
+
}
|
|
104
|
+
function createSuggestedFragments(version, taskId) {
|
|
105
|
+
const displayVersion = version ?? '<version>';
|
|
106
|
+
const displayTask = taskId ?? '<task-id>';
|
|
107
|
+
return [
|
|
108
|
+
{
|
|
109
|
+
path: 'docs/RELEASE_READINESS.md',
|
|
110
|
+
sectionHint: 'Current release section',
|
|
111
|
+
purpose: 'Record source readiness, publish, GitHub Release, and installed-package recycle state.',
|
|
112
|
+
suggestedMarkdown: `| ${displayVersion} closeout | ${displayTask} | Record readiness, publish verification, GitHub Release decision, installed-package recycle, and residual risks. |`
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
path: 'docs/RELEASE_NOTES.md',
|
|
116
|
+
sectionHint: displayVersion,
|
|
117
|
+
purpose: 'Summarize the released version for package consumers.',
|
|
118
|
+
suggestedMarkdown: `## ${displayVersion}\n\n- Summarize user-facing changes.\n- Record npm publish/recycle status and any explicit deferrals.`
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
path: 'docs/PROJECT_STATE.md',
|
|
122
|
+
sectionHint: 'Follow-up note',
|
|
123
|
+
purpose: 'Carry forward release state into current project status.',
|
|
124
|
+
suggestedMarkdown: `${displayTask} follow-up note: ${displayVersion} release closeout state was reviewed. Record readiness, publish, GitHub Release, recycle, and next release-line decision.`
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
path: 'docs/AGENT_HANDOFF.md',
|
|
128
|
+
sectionHint: 'Current State / Last 3 Completed Tasks',
|
|
129
|
+
purpose: 'Route the next agent after release closeout.',
|
|
130
|
+
suggestedMarkdown: `| Latest Completed Task | ${displayTask} ${displayVersion} release closeout | Record closeout result and next release-line task. |`
|
|
131
|
+
}
|
|
132
|
+
];
|
|
133
|
+
}
|
|
134
|
+
function toPortablePath(value) {
|
|
135
|
+
return value.split(node_path_1.default.sep).join('/');
|
|
136
|
+
}
|
|
@@ -14,7 +14,7 @@ function createTaskFinalizeReport(projectRoot, taskId, options = {}) {
|
|
|
14
14
|
const actor = options.actor ?? (0, lifecycle_next_actions_1.defaultTaskLifecycleActor)();
|
|
15
15
|
const reports = createFinalizeReports(projectRoot, taskId, actor);
|
|
16
16
|
const steps = createSteps(taskId, reports);
|
|
17
|
-
const issues = collectIssues(reports);
|
|
17
|
+
const issues = collectIssues(taskId, reports);
|
|
18
18
|
const planHash = hashPlan(taskId, steps);
|
|
19
19
|
if (options.executeRequested)
|
|
20
20
|
return executeFinalizePlan(projectRoot, taskId, actor, reports, steps, issues, planHash, options.planHash);
|
|
@@ -100,7 +100,7 @@ function executeFinalizePlan(projectRoot, taskId, actor, initialReports, initial
|
|
|
100
100
|
function createPostExecutionReport(projectRoot, taskId, actor, requestedPlanHash, reviewedPlanHash, executedSteps, stoppedAt) {
|
|
101
101
|
const reports = createFinalizeReports(projectRoot, taskId, actor);
|
|
102
102
|
const steps = createSteps(taskId, reports);
|
|
103
|
-
const issues = collectIssues(reports);
|
|
103
|
+
const issues = collectIssues(taskId, reports);
|
|
104
104
|
const nextAction = createPrimaryNextAction(taskId, steps, issues);
|
|
105
105
|
const finalAudit = reports.audit?.auditVerdict.verdict === 'closed-valid';
|
|
106
106
|
const execution = {
|
|
@@ -178,7 +178,7 @@ function createSteps(taskId, reports) {
|
|
|
178
178
|
? 'required'
|
|
179
179
|
: 'blocked'
|
|
180
180
|
: 'pending';
|
|
181
|
-
const auditStatus = reports.audit ? (reports.audit.auditVerdict.closeEvidenceFound ? (reports.audit.
|
|
181
|
+
const auditStatus = reports.audit ? (reports.audit.auditVerdict.closeEvidenceFound ? (reports.audit.auditVerdict.verdict === 'closed-valid' ? 'satisfied' : 'required') : 'pending') : 'pending';
|
|
182
182
|
return [
|
|
183
183
|
{
|
|
184
184
|
id: 'finish',
|
|
@@ -216,7 +216,13 @@ function createSteps(taskId, reports) {
|
|
|
216
216
|
{
|
|
217
217
|
id: 'audit-close',
|
|
218
218
|
status: auditStatus,
|
|
219
|
-
summary: auditStatus === 'satisfied'
|
|
219
|
+
summary: auditStatus === 'satisfied'
|
|
220
|
+
? 'Close audit passed.'
|
|
221
|
+
: auditStatus === 'pending'
|
|
222
|
+
? 'Audit waits for close evidence.'
|
|
223
|
+
: reports.audit?.auditVerdict.verdict === 'closed-with-drift-warnings'
|
|
224
|
+
? 'Close audit found close-source drift; review repair plan, then append fresh close proof.'
|
|
225
|
+
: 'Run close audit and repair any drift.',
|
|
220
226
|
command: `hadara task audit-close --task ${taskId} --json`,
|
|
221
227
|
mode: 'read-only',
|
|
222
228
|
writeBoundary: 'read-only',
|
|
@@ -226,7 +232,7 @@ function createSteps(taskId, reports) {
|
|
|
226
232
|
}
|
|
227
233
|
];
|
|
228
234
|
}
|
|
229
|
-
function collectIssues(reports) {
|
|
235
|
+
function collectIssues(taskId, reports) {
|
|
230
236
|
const seen = new Set();
|
|
231
237
|
const issues = [];
|
|
232
238
|
const reportIssues = [...reports.finish.issues, ...(reports.ready?.issues ?? []), ...(reports.close?.issues ?? []), ...(reports.audit?.issues ?? [])];
|
|
@@ -255,6 +261,15 @@ function collectIssues(reports) {
|
|
|
255
261
|
example: 'hadara evidence add-command --task T-XXXX --summary "Focused validation passed." --result passed --category validation --json'
|
|
256
262
|
});
|
|
257
263
|
}
|
|
264
|
+
if (issues.some(isCloseDriftIssue)) {
|
|
265
|
+
issues.push({
|
|
266
|
+
severity: 'info',
|
|
267
|
+
code: 'TASK_FINALIZE_CLOSE_SOURCE_DRIFT_GUIDANCE',
|
|
268
|
+
message: 'Close-source files changed after the recorded close proof. Review the repair plan, finish intended doc edits, then append a fresh close proof and audit it.',
|
|
269
|
+
fixHint: 'Use close-repair-plan for the read-only diagnosis, then rerun the guarded close/finalize path after close-source edits are complete.',
|
|
270
|
+
example: `hadara task close-repair-plan --task ${taskId} --json`
|
|
271
|
+
});
|
|
272
|
+
}
|
|
258
273
|
return issues;
|
|
259
274
|
}
|
|
260
275
|
function createPrimaryNextAction(taskId, steps, issues) {
|
|
@@ -274,6 +289,19 @@ function createPrimaryNextAction(taskId, steps, issues) {
|
|
|
274
289
|
stalePlanRisk: 'low'
|
|
275
290
|
});
|
|
276
291
|
}
|
|
292
|
+
if (nextStep.id === 'audit-close' && issues.some(isCloseDriftIssue)) {
|
|
293
|
+
return (0, lifecycle_next_actions_1.createTaskLifecycleNextAction)({
|
|
294
|
+
id: 'finalize-review-close-repair-plan',
|
|
295
|
+
kind: 'command',
|
|
296
|
+
required: true,
|
|
297
|
+
command: `hadara task close-repair-plan --task ${taskId} --json`,
|
|
298
|
+
message: 'Close-source drift was detected. Review the close repair plan before appending fresh close proof.',
|
|
299
|
+
writeBoundary: 'read-only',
|
|
300
|
+
recommendedActorRole: 'reviewer',
|
|
301
|
+
requiresBeforeHash: false,
|
|
302
|
+
stalePlanRisk: 'none'
|
|
303
|
+
});
|
|
304
|
+
}
|
|
277
305
|
return (0, lifecycle_next_actions_1.createTaskLifecycleNextAction)({
|
|
278
306
|
id: `finalize-${nextStep.id}`,
|
|
279
307
|
kind: nextStep.status === 'blocked' ? 'review' : 'command',
|
|
@@ -286,6 +314,9 @@ function createPrimaryNextAction(taskId, steps, issues) {
|
|
|
286
314
|
stalePlanRisk: nextStep.writeBoundary === 'read-only' ? 'none' : 'low'
|
|
287
315
|
});
|
|
288
316
|
}
|
|
317
|
+
function isCloseDriftIssue(issue) {
|
|
318
|
+
return issue.code === 'TASK_CLOSE_AUDIT_SOURCE_HASH_DRIFT' || issue.code === 'TASK_CLOSE_AUDIT_CURRENT_REPORT_HASH_DRIFT';
|
|
319
|
+
}
|
|
289
320
|
function summarizeSteps(steps, reports) {
|
|
290
321
|
const evaluatedReports = evaluatedReportNames(steps, reports);
|
|
291
322
|
const skippedReports = ['finish', 'ready', 'close', 'audit-close'].filter((name) => !evaluatedReports.includes(name));
|
|
@@ -61,7 +61,7 @@ function createChecks(taskId, reports) {
|
|
|
61
61
|
status: reports.finish.ok ? (reports.finish.summary.plannedWrites > 0 ? 'required' : 'satisfied') : 'blocked',
|
|
62
62
|
summary: reports.finish.summary.plannedWrites > 0 ? 'Task status bookkeeping has planned writes.' : reports.finish.ok ? 'Task finish bookkeeping is current.' : 'Task finish blockers exist.',
|
|
63
63
|
sourceReport: 'hadara.task.finish.v1',
|
|
64
|
-
|
|
64
|
+
...(reports.finish.summary.plannedWrites > 0 ? { command: `hadara task finish --task ${taskId} --json` } : {})
|
|
65
65
|
},
|
|
66
66
|
sharedDocs: {
|
|
67
67
|
status: sharedDocsPending.length > 0 ? 'warning' : reports.finish.ok ? 'satisfied' : 'pending',
|
|
@@ -78,11 +78,11 @@ function createChecks(taskId, reports) {
|
|
|
78
78
|
status: reports.audit.auditVerdict.closeEvidenceFound ? 'satisfied' : reports.close.ok ? 'required' : 'blocked',
|
|
79
79
|
summary: reports.audit.auditVerdict.closeEvidenceFound ? 'Close evidence exists.' : reports.close.ok ? 'Close evidence has not been appended.' : 'Close preconditions have blockers.',
|
|
80
80
|
sourceReport: 'hadara.task.close.v1',
|
|
81
|
-
|
|
81
|
+
...(reports.audit.auditVerdict.closeEvidenceFound ? {} : { command: `hadara task close --task ${taskId} --json` })
|
|
82
82
|
},
|
|
83
83
|
audit: {
|
|
84
|
-
status: reports.audit.
|
|
85
|
-
summary: reports.audit.
|
|
84
|
+
status: reports.audit.auditVerdict.verdict === 'closed-valid' ? 'satisfied' : reports.audit.auditVerdict.closeEvidenceFound ? 'required' : 'pending',
|
|
85
|
+
summary: reports.audit.auditVerdict.verdict === 'closed-valid' ? 'Close audit passed.' : reports.audit.auditVerdict.closeEvidenceFound ? 'Close audit requires repair.' : 'Audit waits for close evidence.',
|
|
86
86
|
sourceReport: 'hadara.task.audit_close.v1',
|
|
87
87
|
command: `hadara task audit-close --task ${taskId} --json`
|
|
88
88
|
}
|
|
@@ -103,7 +103,7 @@ function choosePhase(reports, checks, repair) {
|
|
|
103
103
|
return 'ready-required';
|
|
104
104
|
if (!reports.audit.auditVerdict.closeEvidenceFound)
|
|
105
105
|
return 'close-required';
|
|
106
|
-
if (reports.audit.
|
|
106
|
+
if (reports.audit.auditVerdict.verdict === 'closed-valid')
|
|
107
107
|
return 'closed-valid';
|
|
108
108
|
if (repair && repair.classification !== 'closed-valid')
|
|
109
109
|
return 'repair-required';
|
|
@@ -133,7 +133,7 @@ function chooseNextAction(taskId, phase, reports, checks, repair) {
|
|
|
133
133
|
return undefined;
|
|
134
134
|
}
|
|
135
135
|
function createRepair(taskId, audit) {
|
|
136
|
-
if (audit.
|
|
136
|
+
if (audit.auditVerdict.verdict === 'closed-valid') {
|
|
137
137
|
return {
|
|
138
138
|
classification: 'closed-valid',
|
|
139
139
|
summary: 'Close proof is current and valid.',
|