agentxchain 2.116.0 → 2.118.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 +1 -1
- package/bin/agentxchain.js +24 -0
- package/package.json +1 -1
- package/scripts/render-github-release-body.mjs +1 -1
- package/src/commands/events.js +8 -1
- package/src/commands/inject.js +81 -0
- package/src/commands/resume.js +6 -4
- package/src/commands/run.js +13 -0
- package/src/commands/schedule.js +386 -19
- package/src/commands/status.js +55 -0
- package/src/commands/unblock.js +67 -0
- package/src/lib/continuous-run.js +499 -0
- package/src/lib/governed-state.js +37 -1
- package/src/lib/human-escalations.js +434 -0
- package/src/lib/intake.js +243 -11
- package/src/lib/normalized-config.js +37 -0
- package/src/lib/notification-runner.js +3 -1
- package/src/lib/run-events.js +2 -0
- package/src/lib/run-loop.js +17 -0
- package/src/lib/run-provenance.js +4 -0
- package/src/lib/run-schedule.js +45 -0
- package/src/lib/vision-reader.js +229 -0
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vision Reader — parse VISION.md and derive candidate intents.
|
|
3
|
+
*
|
|
4
|
+
* Reads a project-relative VISION.md, extracts sections and goals,
|
|
5
|
+
* compares against existing intake state (completed intents, run history),
|
|
6
|
+
* and produces ranked candidate intents for the continuous loop.
|
|
7
|
+
*
|
|
8
|
+
* IMPORTANT: The vision path is project-relative, never hardcoded to
|
|
9
|
+
* the agentxchain.dev repo. Each governed project has its own VISION.md.
|
|
10
|
+
*
|
|
11
|
+
* Spec: .planning/VISION_DRIVEN_CONTINUOUS_SPEC.md
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { existsSync, readFileSync, readdirSync } from 'node:fs';
|
|
15
|
+
import { join, resolve as pathResolve, isAbsolute } from 'node:path';
|
|
16
|
+
|
|
17
|
+
// ---------------------------------------------------------------------------
|
|
18
|
+
// Parsing
|
|
19
|
+
// ---------------------------------------------------------------------------
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Parse a VISION.md file into structured sections with goals.
|
|
23
|
+
*
|
|
24
|
+
* @param {string} content - Raw markdown content
|
|
25
|
+
* @returns {{ sections: Array<{ heading: string, level: number, goals: string[], raw: string }> }}
|
|
26
|
+
*/
|
|
27
|
+
export function parseVisionDocument(content) {
|
|
28
|
+
if (!content || typeof content !== 'string') {
|
|
29
|
+
return { sections: [] };
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const lines = content.split('\n');
|
|
33
|
+
const sections = [];
|
|
34
|
+
let current = null;
|
|
35
|
+
|
|
36
|
+
for (const line of lines) {
|
|
37
|
+
// Match H2 or H3 headings (## or ###)
|
|
38
|
+
const headingMatch = line.match(/^(#{2,3})\s+(.+)$/);
|
|
39
|
+
if (headingMatch) {
|
|
40
|
+
if (current) sections.push(current);
|
|
41
|
+
current = {
|
|
42
|
+
heading: headingMatch[2].trim(),
|
|
43
|
+
level: headingMatch[1].length,
|
|
44
|
+
goals: [],
|
|
45
|
+
raw: '',
|
|
46
|
+
};
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (current) {
|
|
51
|
+
current.raw += line + '\n';
|
|
52
|
+
|
|
53
|
+
// Extract bullet points as goals
|
|
54
|
+
const bulletMatch = line.match(/^[-*]\s+\*{0,2}(.+?)\*{0,2}\s*$/);
|
|
55
|
+
if (bulletMatch) {
|
|
56
|
+
const goal = bulletMatch[1].trim();
|
|
57
|
+
if (goal.length > 5) { // skip trivially short bullets
|
|
58
|
+
current.goals.push(goal);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (current) sections.push(current);
|
|
65
|
+
|
|
66
|
+
return { sections };
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// ---------------------------------------------------------------------------
|
|
70
|
+
// Evidence comparison
|
|
71
|
+
// ---------------------------------------------------------------------------
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Load completed intent descriptions from the intake directory.
|
|
75
|
+
*
|
|
76
|
+
* @param {string} root - Project root
|
|
77
|
+
* @returns {string[]} Array of completed intent charters/descriptions
|
|
78
|
+
*/
|
|
79
|
+
export function loadCompletedIntentSignals(root) {
|
|
80
|
+
const intentsDir = join(root, '.agentxchain', 'intake', 'intents');
|
|
81
|
+
if (!existsSync(intentsDir)) return [];
|
|
82
|
+
|
|
83
|
+
const signals = [];
|
|
84
|
+
for (const file of readdirSync(intentsDir)) {
|
|
85
|
+
if (!file.endsWith('.json') || file.startsWith('.tmp-')) continue;
|
|
86
|
+
try {
|
|
87
|
+
const intent = JSON.parse(readFileSync(join(intentsDir, file), 'utf8'));
|
|
88
|
+
if (intent.status === 'completed' || intent.status === 'executing') {
|
|
89
|
+
const desc = intent.charter || intent.signal?.description || '';
|
|
90
|
+
if (desc) signals.push(desc.toLowerCase());
|
|
91
|
+
}
|
|
92
|
+
} catch {
|
|
93
|
+
// skip corrupt files
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return signals;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Load existing intent signals (all statuses except suppressed/rejected) for dedup.
|
|
101
|
+
*
|
|
102
|
+
* @param {string} root - Project root
|
|
103
|
+
* @returns {string[]} Array of active intent charters/descriptions
|
|
104
|
+
*/
|
|
105
|
+
export function loadActiveIntentSignals(root) {
|
|
106
|
+
const intentsDir = join(root, '.agentxchain', 'intake', 'intents');
|
|
107
|
+
if (!existsSync(intentsDir)) return [];
|
|
108
|
+
|
|
109
|
+
const signals = [];
|
|
110
|
+
const skip = new Set(['suppressed', 'rejected']);
|
|
111
|
+
for (const file of readdirSync(intentsDir)) {
|
|
112
|
+
if (!file.endsWith('.json') || file.startsWith('.tmp-')) continue;
|
|
113
|
+
try {
|
|
114
|
+
const intent = JSON.parse(readFileSync(join(intentsDir, file), 'utf8'));
|
|
115
|
+
if (!skip.has(intent.status)) {
|
|
116
|
+
const desc = intent.charter || '';
|
|
117
|
+
if (desc) signals.push(desc.toLowerCase());
|
|
118
|
+
}
|
|
119
|
+
} catch {
|
|
120
|
+
// skip corrupt files
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return signals;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Check whether a vision goal appears to be addressed by existing work.
|
|
128
|
+
*
|
|
129
|
+
* Uses keyword overlap: if >= 60% of significant words in the goal
|
|
130
|
+
* appear in any completed intent description, the goal is considered addressed.
|
|
131
|
+
*
|
|
132
|
+
* @param {string} goal - The vision goal text
|
|
133
|
+
* @param {string[]} completedSignals - Lowercased completed intent descriptions
|
|
134
|
+
* @returns {boolean}
|
|
135
|
+
*/
|
|
136
|
+
export function isGoalAddressed(goal, completedSignals) {
|
|
137
|
+
const words = extractSignificantWords(goal);
|
|
138
|
+
if (words.length === 0) return false;
|
|
139
|
+
|
|
140
|
+
for (const signal of completedSignals) {
|
|
141
|
+
const matchCount = words.filter(w => signal.includes(w)).length;
|
|
142
|
+
if (matchCount / words.length >= 0.6) return true;
|
|
143
|
+
}
|
|
144
|
+
return false;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const STOP_WORDS = new Set([
|
|
148
|
+
'the', 'a', 'an', 'and', 'or', 'but', 'in', 'on', 'at', 'to', 'for',
|
|
149
|
+
'of', 'with', 'by', 'from', 'is', 'are', 'was', 'were', 'be', 'been',
|
|
150
|
+
'being', 'have', 'has', 'had', 'do', 'does', 'did', 'will', 'would',
|
|
151
|
+
'could', 'should', 'may', 'might', 'must', 'shall', 'can', 'that',
|
|
152
|
+
'this', 'these', 'those', 'it', 'its', 'they', 'them', 'their',
|
|
153
|
+
'not', 'no', 'nor', 'only', 'also', 'just', 'than', 'then',
|
|
154
|
+
'each', 'every', 'all', 'any', 'both', 'such', 'as', 'more',
|
|
155
|
+
]);
|
|
156
|
+
|
|
157
|
+
function extractSignificantWords(text) {
|
|
158
|
+
return text
|
|
159
|
+
.toLowerCase()
|
|
160
|
+
.replace(/[^a-z0-9\s-]/g, '')
|
|
161
|
+
.split(/\s+/)
|
|
162
|
+
.filter(w => w.length > 2 && !STOP_WORDS.has(w));
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// ---------------------------------------------------------------------------
|
|
166
|
+
// Candidate derivation
|
|
167
|
+
// ---------------------------------------------------------------------------
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Derive candidate intents from a VISION.md file.
|
|
171
|
+
*
|
|
172
|
+
* @param {string} root - Project root
|
|
173
|
+
* @param {string} visionPath - Absolute path to VISION.md
|
|
174
|
+
* @returns {{ ok: boolean, candidates: Array<{ section: string, goal: string, priority: string }>, error?: string }}
|
|
175
|
+
*/
|
|
176
|
+
export function deriveVisionCandidates(root, visionPath) {
|
|
177
|
+
if (!existsSync(visionPath)) {
|
|
178
|
+
return {
|
|
179
|
+
ok: false,
|
|
180
|
+
candidates: [],
|
|
181
|
+
error: `VISION.md not found at ${visionPath}. Create a .planning/VISION.md for your project to enable vision-driven operation.`,
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
let content;
|
|
186
|
+
try {
|
|
187
|
+
content = readFileSync(visionPath, 'utf8');
|
|
188
|
+
} catch (err) {
|
|
189
|
+
return { ok: false, candidates: [], error: `Cannot read VISION.md: ${err.message}` };
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const { sections } = parseVisionDocument(content);
|
|
193
|
+
if (sections.length === 0) {
|
|
194
|
+
return { ok: false, candidates: [], error: 'VISION.md has no extractable sections.' };
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const completedSignals = loadCompletedIntentSignals(root);
|
|
198
|
+
const activeSignals = loadActiveIntentSignals(root);
|
|
199
|
+
const allSignals = [...completedSignals, ...activeSignals];
|
|
200
|
+
|
|
201
|
+
const candidates = [];
|
|
202
|
+
|
|
203
|
+
for (const section of sections) {
|
|
204
|
+
for (const goal of section.goals) {
|
|
205
|
+
// Skip if this goal is already addressed
|
|
206
|
+
if (isGoalAddressed(goal, allSignals)) continue;
|
|
207
|
+
|
|
208
|
+
candidates.push({
|
|
209
|
+
section: section.heading,
|
|
210
|
+
goal,
|
|
211
|
+
priority: 'p2', // default; operators can override via triage_approval: "human"
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
return { ok: true, candidates };
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Resolve a vision path relative to the project root.
|
|
221
|
+
*
|
|
222
|
+
* @param {string} root - Project root
|
|
223
|
+
* @param {string} visionPath - Path (absolute or project-relative)
|
|
224
|
+
* @returns {string} Absolute path
|
|
225
|
+
*/
|
|
226
|
+
export function resolveVisionPath(root, visionPath) {
|
|
227
|
+
if (isAbsolute(visionPath)) return visionPath;
|
|
228
|
+
return pathResolve(root, visionPath);
|
|
229
|
+
}
|