hadara 0.3.2 → 0.3.3-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 +38 -23
- package/dist/cli/context.js +110 -0
- package/dist/cli/help.js +6 -7
- package/dist/cli/init.js +56 -58
- package/dist/cli/main.js +12 -0
- package/dist/cli/session.js +37 -0
- package/dist/cli/task.js +52 -0
- package/dist/context/code-graph-extractor.js +123 -0
- package/dist/context/code-index.js +1154 -0
- package/dist/context/context-cache-store.js +925 -0
- package/dist/context/context-graph-builder.js +341 -0
- package/dist/context/context-graph.js +42 -0
- package/dist/context/context-pack.js +457 -0
- package/dist/context/context-slice-boundary.js +37 -0
- package/dist/context/context-slice.js +487 -0
- package/dist/context/document-extractors.js +343 -0
- package/dist/context/evidence-extractors.js +179 -0
- package/dist/context/extractor-contract.js +166 -0
- package/dist/context/registry-extractors.js +177 -0
- package/dist/context/release-extractors.js +175 -0
- package/dist/context/session-start.js +297 -0
- package/dist/context/source-manifest.js +566 -0
- package/dist/context/state-projection.js +196 -0
- package/dist/context/task-extractors.js +168 -0
- package/dist/core/schema.js +26 -0
- package/dist/harness/validate.js +7 -8
- package/dist/schemas/code-index.schema.json +173 -0
- package/dist/schemas/context-cache-record.schema.json +56 -0
- package/dist/schemas/context-cache-status.schema.json +167 -0
- package/dist/schemas/context-cache-warm.schema.json +222 -0
- package/dist/schemas/context-graph.schema.json +286 -0
- package/dist/schemas/context-pack.schema.json +246 -0
- package/dist/schemas/context-slice.schema.json +94 -0
- package/dist/schemas/context-source-manifest.schema.json +147 -0
- package/dist/schemas/schema-index.json +91 -0
- package/dist/schemas/session-start.schema.json +158 -0
- package/dist/schemas/task-close-repair-plan.schema.json +67 -0
- package/dist/schemas/task-context.schema.json +125 -0
- package/dist/schemas/task-finalize.schema.json +98 -0
- package/dist/schemas/task-lifecycle.schema.json +84 -0
- package/dist/services/capability-registry.js +266 -9
- package/dist/services/lifecycle-guide.js +23 -29
- package/dist/services/protocol-consistency.js +6 -8
- package/dist/task/acceptance.js +171 -0
- package/dist/task/task-close-repair-plan.js +190 -0
- package/dist/task/task-close.js +34 -35
- package/dist/task/task-finalize.js +377 -0
- package/dist/task/task-lifecycle.js +210 -0
- package/dist/task/task-next.js +10 -1
- package/dist/task/task-ready.js +4 -0
- package/package.json +1 -1
|
@@ -0,0 +1,177 @@
|
|
|
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.extractDocsRegistry = extractDocsRegistry;
|
|
7
|
+
exports.extractCommandRegistry = extractCommandRegistry;
|
|
8
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
9
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
10
|
+
const extractor_contract_1 = require("./extractor-contract");
|
|
11
|
+
const docs_registry_1 = require("../services/docs-registry");
|
|
12
|
+
const capability_registry_1 = require("../services/capability-registry");
|
|
13
|
+
const COMMAND_REGISTRY_SOURCE_PATH = 'src/services/capability-registry.ts';
|
|
14
|
+
function extractDocsRegistry(projectRoot) {
|
|
15
|
+
const absolutePath = node_path_1.default.join(projectRoot, docs_registry_1.DOCS_REGISTRY_PATH);
|
|
16
|
+
const content = readOptionalText(absolutePath);
|
|
17
|
+
const result = (0, extractor_contract_1.createEmptyExtractionResult)('extractDocsRegistry', [{ path: docs_registry_1.DOCS_REGISTRY_PATH, content }]);
|
|
18
|
+
if (content == null) {
|
|
19
|
+
result.issues.push({
|
|
20
|
+
severity: 'warning',
|
|
21
|
+
code: 'CONTEXT_GRAPH_DOC_REGISTRY_MISSING',
|
|
22
|
+
path: docs_registry_1.DOCS_REGISTRY_PATH,
|
|
23
|
+
message: '.hadara/docs-registry.json is missing; Document nodes cannot be extracted from registry metadata.',
|
|
24
|
+
fixHint: 'Restore the docs registry artifact or run the scoped docs registry workflow before relying on document context routing.'
|
|
25
|
+
});
|
|
26
|
+
return result;
|
|
27
|
+
}
|
|
28
|
+
let registry;
|
|
29
|
+
try {
|
|
30
|
+
registry = JSON.parse(content);
|
|
31
|
+
}
|
|
32
|
+
catch (error) {
|
|
33
|
+
result.issues.push(parseFailedIssue(docs_registry_1.DOCS_REGISTRY_PATH, `.hadara/docs-registry.json could not be parsed: ${error instanceof Error ? error.message : String(error)}`));
|
|
34
|
+
return result;
|
|
35
|
+
}
|
|
36
|
+
const sourceHash = (0, extractor_contract_1.hashContextGraphText)(content);
|
|
37
|
+
const source = (0, extractor_contract_1.createContextGraphSourceRef)({
|
|
38
|
+
path: docs_registry_1.DOCS_REGISTRY_PATH,
|
|
39
|
+
extractor: 'extractDocsRegistry',
|
|
40
|
+
line: 1,
|
|
41
|
+
hash: sourceHash
|
|
42
|
+
});
|
|
43
|
+
const documents = Array.isArray(registry.documents) ? registry.documents : [];
|
|
44
|
+
result.nodes.push(...documents.map((doc) => documentNode(doc, source)));
|
|
45
|
+
result.edges.push(...documents.flatMap((doc) => documentEdges(doc, source)));
|
|
46
|
+
result.stateSources?.push(docsRegistryStateSource(documents, sourceHash));
|
|
47
|
+
return result;
|
|
48
|
+
}
|
|
49
|
+
function extractCommandRegistry(projectRoot) {
|
|
50
|
+
const absolutePath = node_path_1.default.join(projectRoot, COMMAND_REGISTRY_SOURCE_PATH);
|
|
51
|
+
const content = readOptionalText(absolutePath);
|
|
52
|
+
const result = (0, extractor_contract_1.createEmptyExtractionResult)('extractCommandRegistry', [{ path: COMMAND_REGISTRY_SOURCE_PATH, content }]);
|
|
53
|
+
const sourceHash = content == null ? undefined : (0, extractor_contract_1.hashContextGraphText)(content);
|
|
54
|
+
if (content == null) {
|
|
55
|
+
result.issues.push({
|
|
56
|
+
severity: 'warning',
|
|
57
|
+
code: 'CONTEXT_GRAPH_COMMAND_REGISTRY_MISSING',
|
|
58
|
+
path: COMMAND_REGISTRY_SOURCE_PATH,
|
|
59
|
+
message: 'Command registry source file is missing; command metadata is still available from the loaded runtime registry but source hash is unavailable.',
|
|
60
|
+
fixHint: 'Restore src/services/capability-registry.ts in source checkouts before relying on source-addressed command context.'
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
const source = (0, extractor_contract_1.createContextGraphSourceRef)({
|
|
64
|
+
path: COMMAND_REGISTRY_SOURCE_PATH,
|
|
65
|
+
extractor: 'extractCommandRegistry',
|
|
66
|
+
line: 1,
|
|
67
|
+
...(sourceHash ? { hash: sourceHash } : {})
|
|
68
|
+
});
|
|
69
|
+
const commands = (0, capability_registry_1.listCommandRegistryEntries)();
|
|
70
|
+
result.nodes.push(...commands.map((entry) => commandNode(entry, source)));
|
|
71
|
+
result.edges.push(...commands.flatMap((entry) => commandDocEdges(entry, source)));
|
|
72
|
+
return result;
|
|
73
|
+
}
|
|
74
|
+
function documentNode(doc, source) {
|
|
75
|
+
return {
|
|
76
|
+
id: (0, extractor_contract_1.createDocumentNodeId)(doc.path),
|
|
77
|
+
type: 'Document',
|
|
78
|
+
label: doc.title || doc.path,
|
|
79
|
+
path: (0, extractor_contract_1.normalizeContextGraphPath)(doc.path),
|
|
80
|
+
status: doc.status,
|
|
81
|
+
kind: doc.kind,
|
|
82
|
+
owner: doc.owner,
|
|
83
|
+
metadata: {
|
|
84
|
+
requiredReading: doc.requiredReading,
|
|
85
|
+
readWhen: doc.readWhen,
|
|
86
|
+
scope: doc.scope,
|
|
87
|
+
updateOwner: doc.updateOwner,
|
|
88
|
+
closeSourceRole: doc.closeSourceRole,
|
|
89
|
+
supersedes: doc.supersedes,
|
|
90
|
+
supersededBy: doc.supersededBy ?? null
|
|
91
|
+
},
|
|
92
|
+
source
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
function documentEdges(doc, source) {
|
|
96
|
+
const from = (0, extractor_contract_1.createDocumentNodeId)(doc.path);
|
|
97
|
+
const edges = [];
|
|
98
|
+
for (const targetPath of doc.supersedes ?? []) {
|
|
99
|
+
const to = (0, extractor_contract_1.createDocumentNodeId)(targetPath);
|
|
100
|
+
edges.push(edge('SUPERSEDES', from, to, source, `${doc.path} supersedes ${targetPath}.`));
|
|
101
|
+
}
|
|
102
|
+
if (doc.supersededBy) {
|
|
103
|
+
const to = (0, extractor_contract_1.createDocumentNodeId)(doc.supersededBy);
|
|
104
|
+
edges.push(edge('SUPERSEDES', to, from, source, `${doc.supersededBy} supersedes ${doc.path}.`));
|
|
105
|
+
}
|
|
106
|
+
return edges;
|
|
107
|
+
}
|
|
108
|
+
function commandNode(entry, source) {
|
|
109
|
+
return {
|
|
110
|
+
id: (0, extractor_contract_1.createCommandNodeId)(entry.id),
|
|
111
|
+
type: 'Command',
|
|
112
|
+
label: entry.id,
|
|
113
|
+
status: entry.status,
|
|
114
|
+
kind: entry.family,
|
|
115
|
+
owner: entry.actor,
|
|
116
|
+
metadata: {
|
|
117
|
+
command: entry.command,
|
|
118
|
+
summary: entry.summary,
|
|
119
|
+
canonical: entry.canonical,
|
|
120
|
+
aliasFor: entry.aliasFor ?? null,
|
|
121
|
+
scope: entry.scope,
|
|
122
|
+
lifecycleStage: entry.lifecycleStage,
|
|
123
|
+
requiredness: entry.requiredness,
|
|
124
|
+
writeBoundary: entry.writeBoundary,
|
|
125
|
+
readOnly: entry.readOnly,
|
|
126
|
+
risk: entry.risk,
|
|
127
|
+
schemaVersion: entry.schemaVersion ?? null,
|
|
128
|
+
implementationFiles: entry.implementationFiles ?? [],
|
|
129
|
+
testFiles: entry.testFiles ?? [],
|
|
130
|
+
docs: entry.docs,
|
|
131
|
+
related: entry.related
|
|
132
|
+
},
|
|
133
|
+
source
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
function commandDocEdges(entry, source) {
|
|
137
|
+
return entry.docs.map((docPath) => edge('DESCRIBES_COMMAND', (0, extractor_contract_1.createDocumentNodeId)(docPath), (0, extractor_contract_1.createCommandNodeId)(entry.id), source, `${docPath} documents command ${entry.id}.`));
|
|
138
|
+
}
|
|
139
|
+
function docsRegistryStateSource(documents, sourceHash) {
|
|
140
|
+
return {
|
|
141
|
+
id: 'state-source:docs-registry',
|
|
142
|
+
path: docs_registry_1.DOCS_REGISTRY_PATH,
|
|
143
|
+
kind: 'docs-registry',
|
|
144
|
+
hash: sourceHash,
|
|
145
|
+
extracted: {
|
|
146
|
+
documents: documents.length,
|
|
147
|
+
requiredReading: documents.filter((doc) => doc.requiredReading).length,
|
|
148
|
+
statusCounts: documents.reduce((counts, doc) => {
|
|
149
|
+
counts[doc.status] = (counts[doc.status] ?? 0) + 1;
|
|
150
|
+
return counts;
|
|
151
|
+
}, {})
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
function edge(type, from, to, source, reason) {
|
|
156
|
+
return {
|
|
157
|
+
id: (0, extractor_contract_1.createContextGraphEdgeId)({ type, from, to, source, reason }),
|
|
158
|
+
from,
|
|
159
|
+
to,
|
|
160
|
+
type,
|
|
161
|
+
confidence: 'explicit',
|
|
162
|
+
reason,
|
|
163
|
+
source
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
function readOptionalText(absolutePath) {
|
|
167
|
+
return node_fs_1.default.existsSync(absolutePath) ? node_fs_1.default.readFileSync(absolutePath, 'utf8') : null;
|
|
168
|
+
}
|
|
169
|
+
function parseFailedIssue(relativePath, message) {
|
|
170
|
+
return {
|
|
171
|
+
severity: 'warning',
|
|
172
|
+
code: 'CONTEXT_GRAPH_PARSE_FAILED',
|
|
173
|
+
path: relativePath,
|
|
174
|
+
message,
|
|
175
|
+
fixHint: `Repair ${relativePath} before relying on context graph extraction for this source.`
|
|
176
|
+
};
|
|
177
|
+
}
|
|
@@ -0,0 +1,175 @@
|
|
|
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.extractReleaseReadiness = extractReleaseReadiness;
|
|
7
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
8
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
9
|
+
const extractor_contract_1 = require("./extractor-contract");
|
|
10
|
+
const capability_registry_1 = require("../services/capability-registry");
|
|
11
|
+
const RELEASE_READINESS_PATH = 'docs/RELEASE_READINESS.md';
|
|
12
|
+
const EVIDENCE_ID_PATTERN = /\bev:T-\d{4}:[a-f0-9]{24,64}\b/g;
|
|
13
|
+
function extractReleaseReadiness(projectRoot) {
|
|
14
|
+
const content = readOptionalText(node_path_1.default.join(projectRoot, RELEASE_READINESS_PATH));
|
|
15
|
+
const result = (0, extractor_contract_1.createEmptyExtractionResult)('extractReleaseReadiness', [{ path: RELEASE_READINESS_PATH, content }]);
|
|
16
|
+
if (content == null) {
|
|
17
|
+
result.issues.push({
|
|
18
|
+
severity: 'warning',
|
|
19
|
+
code: 'CONTEXT_GRAPH_SOURCE_MISSING',
|
|
20
|
+
path: RELEASE_READINESS_PATH,
|
|
21
|
+
message: 'docs/RELEASE_READINESS.md is missing; ReleaseCheck nodes cannot be extracted.',
|
|
22
|
+
fixHint: 'Restore docs/RELEASE_READINESS.md before relying on release-readiness context routing.'
|
|
23
|
+
});
|
|
24
|
+
return result;
|
|
25
|
+
}
|
|
26
|
+
const sourceHash = (0, extractor_contract_1.hashContextGraphText)(content);
|
|
27
|
+
const commandEntries = (0, capability_registry_1.listCommandRegistryEntries)();
|
|
28
|
+
const sections = parseReleaseReadinessSections(content);
|
|
29
|
+
for (const section of sections) {
|
|
30
|
+
const source = (0, extractor_contract_1.createContextGraphSourceRef)({
|
|
31
|
+
path: RELEASE_READINESS_PATH,
|
|
32
|
+
extractor: 'extractReleaseReadiness',
|
|
33
|
+
line: section.startLine,
|
|
34
|
+
hash: sourceHash
|
|
35
|
+
});
|
|
36
|
+
const checkNodeId = (0, extractor_contract_1.createReleaseCheckNodeId)(section.title);
|
|
37
|
+
const commandMentions = findCommandMentions(section.text, commandEntries);
|
|
38
|
+
const evidenceIds = findEvidenceIds(section.text);
|
|
39
|
+
result.nodes.push(releaseCheckNode(section, source, commandMentions, evidenceIds));
|
|
40
|
+
result.edges.push(edge('BELONGS_TO_DOCUMENT', checkNodeId, (0, extractor_contract_1.createDocumentNodeId)(RELEASE_READINESS_PATH), source, `${section.title} is defined in ${RELEASE_READINESS_PATH}.`));
|
|
41
|
+
for (const mention of commandMentions) {
|
|
42
|
+
result.edges.push(edge('CHECKS_COMMAND', checkNodeId, (0, extractor_contract_1.createCommandNodeId)(mention.commandId), source, `${section.title} explicitly references command ${mention.commandId}.`));
|
|
43
|
+
}
|
|
44
|
+
for (const evidenceId of evidenceIds) {
|
|
45
|
+
result.edges.push(edge('DEPENDS_ON_EVIDENCE', checkNodeId, (0, extractor_contract_1.createEvidenceNodeId)(evidenceId), source, `${section.title} explicitly references evidence ${evidenceId}.`));
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
result.stateSources?.push(releaseReadinessStateSource(sourceHash, sections, result.nodes, result.edges));
|
|
49
|
+
return result;
|
|
50
|
+
}
|
|
51
|
+
function releaseCheckNode(section, source, commandMentions, evidenceIds) {
|
|
52
|
+
const status = deriveSectionStatus(section.text);
|
|
53
|
+
return {
|
|
54
|
+
id: (0, extractor_contract_1.createReleaseCheckNodeId)(section.title),
|
|
55
|
+
type: 'ReleaseCheck',
|
|
56
|
+
label: section.title,
|
|
57
|
+
path: (0, extractor_contract_1.normalizeContextGraphPath)(RELEASE_READINESS_PATH),
|
|
58
|
+
status,
|
|
59
|
+
kind: 'release-readiness-section',
|
|
60
|
+
metadata: {
|
|
61
|
+
startLine: section.startLine,
|
|
62
|
+
endLine: section.endLine,
|
|
63
|
+
commandIds: commandMentions.map((mention) => mention.commandId),
|
|
64
|
+
commandMentions: commandMentions.map((mention) => mention.mention),
|
|
65
|
+
evidenceIds,
|
|
66
|
+
summary: firstMeaningfulLine(section.text)
|
|
67
|
+
},
|
|
68
|
+
source
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
function parseReleaseReadinessSections(content) {
|
|
72
|
+
const lines = content.split(/\r?\n/);
|
|
73
|
+
const headingIndexes = [];
|
|
74
|
+
lines.forEach((line, index) => {
|
|
75
|
+
const match = line.match(/^##\s+(.+?)\s*#*\s*$/);
|
|
76
|
+
if (match?.[1])
|
|
77
|
+
headingIndexes.push({ index, title: match[1].trim() });
|
|
78
|
+
});
|
|
79
|
+
return headingIndexes.map((heading, position) => {
|
|
80
|
+
const nextHeading = headingIndexes[position + 1]?.index ?? lines.length;
|
|
81
|
+
const bodyLines = lines.slice(heading.index + 1, nextHeading);
|
|
82
|
+
return {
|
|
83
|
+
title: heading.title,
|
|
84
|
+
startLine: heading.index + 1,
|
|
85
|
+
endLine: nextHeading,
|
|
86
|
+
text: bodyLines.join('\n').trim()
|
|
87
|
+
};
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
function findCommandMentions(text, entries) {
|
|
91
|
+
const spans = codeSpans(text).filter((span) => /\bhadara\b/.test(span));
|
|
92
|
+
const mentions = new Map();
|
|
93
|
+
for (const span of spans) {
|
|
94
|
+
const normalizedSpan = normalizeCommandText(span);
|
|
95
|
+
for (const entry of entries) {
|
|
96
|
+
const prefix = commandPrefix(entry.command);
|
|
97
|
+
if (!prefix || !normalizedSpan.startsWith(prefix))
|
|
98
|
+
continue;
|
|
99
|
+
mentions.set(entry.id, {
|
|
100
|
+
commandId: entry.id,
|
|
101
|
+
command: entry.command,
|
|
102
|
+
mention: span
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return Array.from(mentions.values()).sort((a, b) => a.commandId.localeCompare(b.commandId));
|
|
107
|
+
}
|
|
108
|
+
function findEvidenceIds(text) {
|
|
109
|
+
return Array.from(new Set(text.match(EVIDENCE_ID_PATTERN) ?? [])).sort();
|
|
110
|
+
}
|
|
111
|
+
function codeSpans(text) {
|
|
112
|
+
const spans = [];
|
|
113
|
+
for (const match of text.matchAll(/`([^`]+)`/g)) {
|
|
114
|
+
if (match[1])
|
|
115
|
+
spans.push(match[1].trim());
|
|
116
|
+
}
|
|
117
|
+
return spans;
|
|
118
|
+
}
|
|
119
|
+
function commandPrefix(command) {
|
|
120
|
+
return normalizeCommandText(command)
|
|
121
|
+
.replace(/\s*\[[^\]]+\]/g, '')
|
|
122
|
+
.replace(/\s*<[^>]+>/g, '')
|
|
123
|
+
.trim();
|
|
124
|
+
}
|
|
125
|
+
function normalizeCommandText(command) {
|
|
126
|
+
return command.replace(/\s+/g, ' ').trim();
|
|
127
|
+
}
|
|
128
|
+
function deriveSectionStatus(text) {
|
|
129
|
+
const lowered = text.toLowerCase();
|
|
130
|
+
if (/\b(deferred|reserved)\b/.test(lowered))
|
|
131
|
+
return 'deferred';
|
|
132
|
+
if (/\b(blocked|blocking)\b/.test(lowered))
|
|
133
|
+
return 'blocked';
|
|
134
|
+
if (/\b(complete|completed|passed|published)\b/.test(lowered))
|
|
135
|
+
return 'current';
|
|
136
|
+
return 'documented';
|
|
137
|
+
}
|
|
138
|
+
function firstMeaningfulLine(text) {
|
|
139
|
+
return text.split(/\r?\n/)
|
|
140
|
+
.map((line) => line.trim().replace(/^-\s+/, ''))
|
|
141
|
+
.find((line) => line.length > 0) ?? null;
|
|
142
|
+
}
|
|
143
|
+
function releaseReadinessStateSource(sourceHash, sections, nodes, edges) {
|
|
144
|
+
return {
|
|
145
|
+
id: 'state-source:release-readiness',
|
|
146
|
+
path: RELEASE_READINESS_PATH,
|
|
147
|
+
kind: 'release-readiness',
|
|
148
|
+
hash: sourceHash,
|
|
149
|
+
extracted: {
|
|
150
|
+
checks: sections.length,
|
|
151
|
+
headings: sections.map((section) => section.title),
|
|
152
|
+
statusCounts: nodes.reduce((counts, node) => {
|
|
153
|
+
const status = node.status ?? 'unknown';
|
|
154
|
+
counts[status] = (counts[status] ?? 0) + 1;
|
|
155
|
+
return counts;
|
|
156
|
+
}, {}),
|
|
157
|
+
commandReferences: edges.filter((edgeItem) => edgeItem.type === 'CHECKS_COMMAND').length,
|
|
158
|
+
evidenceReferences: edges.filter((edgeItem) => edgeItem.type === 'DEPENDS_ON_EVIDENCE').length
|
|
159
|
+
}
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
function edge(type, from, to, source, reason) {
|
|
163
|
+
return {
|
|
164
|
+
id: (0, extractor_contract_1.createContextGraphEdgeId)({ type, from, to, source, reason }),
|
|
165
|
+
from,
|
|
166
|
+
to,
|
|
167
|
+
type,
|
|
168
|
+
confidence: 'explicit',
|
|
169
|
+
reason,
|
|
170
|
+
source
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
function readOptionalText(absolutePath) {
|
|
174
|
+
return node_fs_1.default.existsSync(absolutePath) ? node_fs_1.default.readFileSync(absolutePath, 'utf8') : null;
|
|
175
|
+
}
|
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SESSION_START_COMMAND = exports.SESSION_START_SCHEMA_ID = void 0;
|
|
4
|
+
exports.buildSessionStartReport = buildSessionStartReport;
|
|
5
|
+
const context_pack_1 = require("./context-pack");
|
|
6
|
+
const context_graph_builder_1 = require("./context-graph-builder");
|
|
7
|
+
const context_cache_store_1 = require("./context-cache-store");
|
|
8
|
+
const source_manifest_1 = require("./source-manifest");
|
|
9
|
+
const code_graph_extractor_1 = require("./code-graph-extractor");
|
|
10
|
+
exports.SESSION_START_SCHEMA_ID = 'hadara.sessionStart.v1';
|
|
11
|
+
exports.SESSION_START_COMMAND = 'session.start';
|
|
12
|
+
function buildSessionStartReport(input) {
|
|
13
|
+
const generatedAt = input.generatedAt ?? new Date().toISOString();
|
|
14
|
+
const contextPack = input.contextPack ?? (input.allowLiveContextPack
|
|
15
|
+
? (0, context_pack_1.buildContextPackReport)({
|
|
16
|
+
projectRoot: input.projectRoot,
|
|
17
|
+
generatedAt,
|
|
18
|
+
includeCode: input.includeCode,
|
|
19
|
+
...(input.taskId ? { taskId: input.taskId } : {}),
|
|
20
|
+
...(input.budget ? { budget: input.budget } : {})
|
|
21
|
+
})
|
|
22
|
+
: buildWarmCachedContextPackReport({
|
|
23
|
+
projectRoot: input.projectRoot,
|
|
24
|
+
generatedAt,
|
|
25
|
+
taskId: input.taskId,
|
|
26
|
+
includeCode: input.includeCode,
|
|
27
|
+
budget: input.budget
|
|
28
|
+
}) ?? buildBoundedContextPackReport({
|
|
29
|
+
projectRoot: input.projectRoot,
|
|
30
|
+
generatedAt,
|
|
31
|
+
taskId: input.taskId,
|
|
32
|
+
budget: input.budget
|
|
33
|
+
}));
|
|
34
|
+
const stateProjection = contextPack.stateProjection;
|
|
35
|
+
const taskId = contextPack.taskId ?? input.taskId ?? stateProjection.activeTask;
|
|
36
|
+
const issues = [...contextPack.issues];
|
|
37
|
+
const lifecycle = lifecycleForSessionStart(taskId, contextPack);
|
|
38
|
+
const guidance = guidanceForSessionStart({
|
|
39
|
+
taskId,
|
|
40
|
+
contextPack,
|
|
41
|
+
lifecycle,
|
|
42
|
+
allowLiveContextPack: Boolean(input.allowLiveContextPack)
|
|
43
|
+
});
|
|
44
|
+
return {
|
|
45
|
+
schemaVersion: exports.SESSION_START_SCHEMA_ID,
|
|
46
|
+
command: exports.SESSION_START_COMMAND,
|
|
47
|
+
ok: contextPack.ok && issues.every((issue) => issue.severity !== 'error'),
|
|
48
|
+
generatedAt,
|
|
49
|
+
projectRoot: input.projectRoot,
|
|
50
|
+
currentState: {
|
|
51
|
+
...(stateProjection.activeTask ? { activeTask: stateProjection.activeTask } : {}),
|
|
52
|
+
...(stateProjection.latestCompletedTask ? { latestCompletedTask: stateProjection.latestCompletedTask } : {}),
|
|
53
|
+
...(taskId ? { recommendedNextTask: taskId } : {}),
|
|
54
|
+
...(stateProjection.releaseState ? { releaseState: stateProjection.releaseState } : {})
|
|
55
|
+
},
|
|
56
|
+
contextPack,
|
|
57
|
+
lifecycle,
|
|
58
|
+
guidance,
|
|
59
|
+
knownProblems: contextPack.knownProblems,
|
|
60
|
+
sourceSummary: contextPack.sourceSummary,
|
|
61
|
+
cache: contextPack.cache,
|
|
62
|
+
summary: {
|
|
63
|
+
degraded: contextPack.sourceSummary.degraded || issues.some((issue) => issue.severity !== 'info'),
|
|
64
|
+
readFirstCount: contextPack.readFirst.length,
|
|
65
|
+
readIfNeededCount: contextPack.readIfNeeded.length,
|
|
66
|
+
sliceCandidateCount: contextPack.sliceCandidates.length,
|
|
67
|
+
knownProblemCount: contextPack.knownProblems.length,
|
|
68
|
+
issueCount: issues.length
|
|
69
|
+
},
|
|
70
|
+
issues
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
function buildWarmCachedContextPackReport(input) {
|
|
74
|
+
const cachedManifest = (0, context_cache_store_1.readContextSourceManifestCache)(input.projectRoot);
|
|
75
|
+
if (cachedManifest.status !== 'valid' || !cachedManifest.manifest)
|
|
76
|
+
return undefined;
|
|
77
|
+
const fastFreshness = (0, source_manifest_1.checkContextSourceManifestFastFreshness)(input.projectRoot, cachedManifest.manifest);
|
|
78
|
+
if (!fastFreshness.ok)
|
|
79
|
+
return undefined;
|
|
80
|
+
const graphCore = (0, context_cache_store_1.readContextGraphCoreShard)({
|
|
81
|
+
projectRoot: input.projectRoot,
|
|
82
|
+
manifest: cachedManifest.manifest
|
|
83
|
+
});
|
|
84
|
+
if (!graphCore.hit || !graphCore.result)
|
|
85
|
+
return undefined;
|
|
86
|
+
const extractionResults = [graphCore.result];
|
|
87
|
+
const code = input.includeCode
|
|
88
|
+
? (0, context_cache_store_1.readContextCodeIndexShard)({
|
|
89
|
+
projectRoot: input.projectRoot,
|
|
90
|
+
manifest: cachedManifest.manifest
|
|
91
|
+
})
|
|
92
|
+
: undefined;
|
|
93
|
+
if (code?.hit && code.result) {
|
|
94
|
+
extractionResults.push((0, code_graph_extractor_1.codeIndexReportToGraphExtraction)(code.result));
|
|
95
|
+
}
|
|
96
|
+
const cache = {
|
|
97
|
+
used: true,
|
|
98
|
+
hit: !input.includeCode || Boolean(code?.hit),
|
|
99
|
+
mode: input.includeCode
|
|
100
|
+
? code?.hit ? 'graph-core+code-index' : `graph-core+code-index-${code?.status ?? 'missing'}`
|
|
101
|
+
: 'graph-core',
|
|
102
|
+
manifestHash: cachedManifest.manifest.manifestHash,
|
|
103
|
+
readShardCount: 1 + (input.includeCode ? 1 : 0),
|
|
104
|
+
hitShardCount: 1 + (code?.hit ? 1 : 0),
|
|
105
|
+
missShardCount: code?.status === 'missing' ? 1 : 0,
|
|
106
|
+
staleShardCount: code?.status === 'stale' ? 1 : 0,
|
|
107
|
+
corruptShardCount: code?.status === 'corrupt' ? 1 : 0,
|
|
108
|
+
schemaMismatchShardCount: code?.status === 'schema-mismatch' ? 1 : 0,
|
|
109
|
+
shardPaths: [graphCore.path, ...(code ? [code.path] : [])].sort(),
|
|
110
|
+
staleExtractorKeys: code?.status === 'stale' ? ['codeIndex'] : [],
|
|
111
|
+
...(graphCore.record ? { createdAt: graphCore.record.createdAt, cachePath: graphCore.path } : {}),
|
|
112
|
+
sourceManifestCacheFresh: true,
|
|
113
|
+
sourceManifestFastPath: 'hit',
|
|
114
|
+
sourceManifestFastPathReason: fastFreshness.reason,
|
|
115
|
+
...(fastFreshness.strategy ? { sourceManifestFastPathStrategy: fastFreshness.strategy } : {})
|
|
116
|
+
};
|
|
117
|
+
const graphReport = (0, context_graph_builder_1.buildContextGraphReport)({
|
|
118
|
+
projectRoot: input.projectRoot,
|
|
119
|
+
generatedAt: input.generatedAt,
|
|
120
|
+
...(input.taskId ? { taskId: input.taskId, mode: 'task' } : { mode: 'full' }),
|
|
121
|
+
extractionResults,
|
|
122
|
+
cache
|
|
123
|
+
});
|
|
124
|
+
return (0, context_pack_1.buildContextPackReport)({
|
|
125
|
+
projectRoot: input.projectRoot,
|
|
126
|
+
generatedAt: input.generatedAt,
|
|
127
|
+
...(input.taskId ? { taskId: input.taskId } : {}),
|
|
128
|
+
...(input.budget ? { budget: input.budget } : {}),
|
|
129
|
+
includeCode: input.includeCode,
|
|
130
|
+
graphReport,
|
|
131
|
+
cache
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
function lifecycleForSessionStart(taskId, contextPack) {
|
|
135
|
+
const primaryNextCommands = taskId
|
|
136
|
+
? [
|
|
137
|
+
`node dist/cli/main.js task status --task ${taskId} --json`,
|
|
138
|
+
`node dist/cli/main.js context pack --task ${taskId} --json`
|
|
139
|
+
]
|
|
140
|
+
: ['node dist/cli/main.js task next --json'];
|
|
141
|
+
for (const suggestion of contextPack.validateWith) {
|
|
142
|
+
if (suggestion.requiredForClose && !primaryNextCommands.includes(suggestion.command)) {
|
|
143
|
+
primaryNextCommands.push(suggestion.command);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
const diagnosticCommands = [
|
|
147
|
+
'node dist/cli/main.js context cache status --json',
|
|
148
|
+
taskId
|
|
149
|
+
? `node dist/cli/main.js context graph --task ${taskId} --json`
|
|
150
|
+
: 'node dist/cli/main.js context graph --json',
|
|
151
|
+
taskId
|
|
152
|
+
? `node dist/cli/main.js session start --task ${taskId} --live --json`
|
|
153
|
+
: 'node dist/cli/main.js session start --live --json',
|
|
154
|
+
'node dist/cli/main.js state verify --json'
|
|
155
|
+
];
|
|
156
|
+
return {
|
|
157
|
+
primaryNextCommands,
|
|
158
|
+
diagnosticCommands
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
function guidanceForSessionStart(input) {
|
|
162
|
+
const mode = input.allowLiveContextPack
|
|
163
|
+
? 'live-context-pack'
|
|
164
|
+
: input.contextPack.cache.used
|
|
165
|
+
? 'warm-cache'
|
|
166
|
+
: 'bounded-no-live';
|
|
167
|
+
const taskId = input.taskId;
|
|
168
|
+
const taskRequired = !taskId;
|
|
169
|
+
const primaryNextAction = taskRequired ? 'select-task' : 'inspect-task';
|
|
170
|
+
const reason = taskRequired
|
|
171
|
+
? 'No task id was supplied, so bounded Session Start returned task-selection guidance without running live graph discovery.'
|
|
172
|
+
: mode === 'warm-cache'
|
|
173
|
+
? 'Session Start used proven-fresh warm cache and preserved read-only behavior.'
|
|
174
|
+
: mode === 'live-context-pack'
|
|
175
|
+
? 'Session Start used explicit live context-pack discovery because --live was supplied.'
|
|
176
|
+
: 'Session Start used the bounded no-live packet and avoided broad live graph discovery.';
|
|
177
|
+
const commands = [];
|
|
178
|
+
if (!taskId) {
|
|
179
|
+
commands.push({
|
|
180
|
+
id: 'task-next',
|
|
181
|
+
command: 'node dist/cli/main.js task next --json',
|
|
182
|
+
args: ['task', 'next', '--json'],
|
|
183
|
+
reason: 'Choose the next task before requesting task-scoped context.'
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
else {
|
|
187
|
+
commands.push({
|
|
188
|
+
id: 'task-status',
|
|
189
|
+
command: `node dist/cli/main.js task status --task ${taskId} --json`,
|
|
190
|
+
args: ['task', 'status', '--task', taskId, '--json'],
|
|
191
|
+
reason: 'Inspect task readiness, evidence, and lifecycle state.'
|
|
192
|
+
});
|
|
193
|
+
commands.push({
|
|
194
|
+
id: 'context-pack',
|
|
195
|
+
command: `node dist/cli/main.js context pack --task ${taskId} --json`,
|
|
196
|
+
args: ['context', 'pack', '--task', taskId, '--json'],
|
|
197
|
+
reason: 'Inspect the bounded task read plan without slicing raw source text.'
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
commands.push({
|
|
201
|
+
id: 'cache-warm',
|
|
202
|
+
command: 'node dist/cli/main.js context cache warm --json',
|
|
203
|
+
args: ['context', 'cache', 'warm', '--json'],
|
|
204
|
+
reason: 'Preview stale cache shards without writing cache.'
|
|
205
|
+
});
|
|
206
|
+
commands.push({
|
|
207
|
+
id: 'session-start-live',
|
|
208
|
+
command: taskId
|
|
209
|
+
? `node dist/cli/main.js session start --task ${taskId} --live --json`
|
|
210
|
+
: 'node dist/cli/main.js session start --live --json',
|
|
211
|
+
args: taskId
|
|
212
|
+
? ['session', 'start', '--task', taskId, '--live', '--json']
|
|
213
|
+
: ['session', 'start', '--live', '--json'],
|
|
214
|
+
reason: 'Opt into slower live context-pack discovery only when broad graph reads are acceptable.'
|
|
215
|
+
});
|
|
216
|
+
return {
|
|
217
|
+
mode,
|
|
218
|
+
primaryNextAction,
|
|
219
|
+
reason,
|
|
220
|
+
taskRequired,
|
|
221
|
+
liveContextPackAvailable: true,
|
|
222
|
+
commands
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
function buildBoundedContextPackReport(input) {
|
|
226
|
+
const budget = {
|
|
227
|
+
...(input.budget?.targetTokens !== undefined ? { targetTokens: input.budget.targetTokens } : {}),
|
|
228
|
+
...(input.budget?.maxItems !== undefined ? { maxItems: input.budget.maxItems } : {}),
|
|
229
|
+
maxReadFirstItems: input.budget?.maxReadFirstItems ?? 7,
|
|
230
|
+
mode: input.budget?.mode ?? 'bounded'
|
|
231
|
+
};
|
|
232
|
+
const issues = input.taskId
|
|
233
|
+
? [{
|
|
234
|
+
severity: 'warning',
|
|
235
|
+
code: 'CONTEXT_PACK_DEGRADED',
|
|
236
|
+
message: 'Session start used the bounded no-live context pack envelope. Run session start --live or context pack explicitly when a full graph read is acceptable.',
|
|
237
|
+
fixHint: 'Run hadara context cache warm --execute --json before relying on broad graph-backed session context.'
|
|
238
|
+
}]
|
|
239
|
+
: [{
|
|
240
|
+
severity: 'warning',
|
|
241
|
+
code: 'CONTEXT_PACK_TASK_NOT_FOUND',
|
|
242
|
+
message: 'No task id was supplied. Bounded session start returned task-selection guidance without running live project discovery.',
|
|
243
|
+
fixHint: 'Run hadara task next --json, then rerun hadara session start --task <task-id> --json.'
|
|
244
|
+
}];
|
|
245
|
+
const readFirst = input.taskId
|
|
246
|
+
? [{
|
|
247
|
+
id: `task:${input.taskId}`,
|
|
248
|
+
type: 'Task',
|
|
249
|
+
title: input.taskId,
|
|
250
|
+
reason: 'Explicit task id supplied to bounded session start.',
|
|
251
|
+
confidence: 'explicit',
|
|
252
|
+
required: true
|
|
253
|
+
}]
|
|
254
|
+
: [];
|
|
255
|
+
return {
|
|
256
|
+
schemaVersion: context_pack_1.CONTEXT_PACK_SCHEMA_ID,
|
|
257
|
+
command: context_pack_1.CONTEXT_PACK_COMMAND,
|
|
258
|
+
ok: issues.every((issue) => issue.severity !== 'error'),
|
|
259
|
+
generatedAt: input.generatedAt,
|
|
260
|
+
...(input.taskId ? { taskId: input.taskId } : {}),
|
|
261
|
+
projectRoot: input.projectRoot,
|
|
262
|
+
budget,
|
|
263
|
+
readFirst: readFirst.slice(0, budget.maxReadFirstItems),
|
|
264
|
+
readIfNeeded: [],
|
|
265
|
+
doNotReadByDefault: [],
|
|
266
|
+
validateWith: input.taskId
|
|
267
|
+
? [{
|
|
268
|
+
command: `node dist/cli/main.js task ready --task ${input.taskId} --level done --json`,
|
|
269
|
+
reason: 'Done-level readiness is required before closing this task.',
|
|
270
|
+
requiredForClose: true,
|
|
271
|
+
source: 'evidence-history'
|
|
272
|
+
}]
|
|
273
|
+
: [],
|
|
274
|
+
writeBoundaries: [],
|
|
275
|
+
sliceCandidates: [],
|
|
276
|
+
knownProblems: [],
|
|
277
|
+
stateProjection: {
|
|
278
|
+
...(input.taskId ? { activeTask: input.taskId } : {}),
|
|
279
|
+
stateConsistency: 'unknown',
|
|
280
|
+
issues: []
|
|
281
|
+
},
|
|
282
|
+
sourceSummary: {
|
|
283
|
+
graphAvailable: false,
|
|
284
|
+
codeIndexAvailable: false,
|
|
285
|
+
stateProjectionAvailable: false,
|
|
286
|
+
docsRegistryAvailable: false,
|
|
287
|
+
commandRegistryAvailable: false,
|
|
288
|
+
degraded: true
|
|
289
|
+
},
|
|
290
|
+
cache: {
|
|
291
|
+
used: false,
|
|
292
|
+
hit: false,
|
|
293
|
+
mode: 'session-start-bounded-no-live'
|
|
294
|
+
},
|
|
295
|
+
issues
|
|
296
|
+
};
|
|
297
|
+
}
|