substrate-ai 0.20.63 → 0.20.65
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/dist/adapter-registry-BbVWH3Yv.js +4 -0
- package/dist/cli/index.js +93 -23
- package/dist/decision-router-DblHY8se.js +97 -0
- package/dist/{decisions-4F91LrVD.js → decisions-DilHo99V.js} +2 -2
- package/dist/{dist-W2emvN3F.js → dist-K_RRWnBX.js} +2 -2
- package/dist/{errors-CKFu8YI9.js → errors-pSiZbn6e.js} +2 -2
- package/dist/{experimenter-DxxwicpK.js → experimenter-DT9v2Pto.js} +1 -1
- package/dist/health-DC3y-sR6.js +1715 -0
- package/dist/health-qhtWYh49.js +8 -0
- package/dist/index-c924O9mj.d.ts +1432 -0
- package/dist/index.d.ts +132 -736
- package/dist/index.js +2 -2
- package/dist/interactive-prompt-C7wpE4z4.js +183 -0
- package/dist/{health-C-KOZrFJ.js → manifest-read-DDkXC3L_.js} +130 -2013
- package/dist/modules/interactive-prompt/index.d.ts +86 -0
- package/dist/modules/interactive-prompt/index.js +6 -0
- package/dist/recovery-engine-BKGBeBnW.js +281 -0
- package/dist/{routing-0ykvBl_4.js → routing-CzF0p6lI.js} +2 -2
- package/dist/run-DX95j4_D.js +14 -0
- package/dist/{run-CHUFlRbH.js → run-DzB4rgkj.js} +391 -31
- package/dist/src/modules/decision-router/index.d.ts +88 -0
- package/dist/src/modules/decision-router/index.js +3 -0
- package/dist/src/modules/recovery-engine/index.d.ts +1101 -0
- package/dist/src/modules/recovery-engine/index.js +5 -0
- package/dist/{upgrade-C8LAnB6W.js → upgrade-DxzQ1nss.js} +3 -3
- package/dist/{upgrade-CAqLkNUP.js → upgrade-MP9XzrI6.js} +2 -2
- package/dist/version-manager-impl-GZDUBt0Q.js +4 -0
- package/dist/work-graph-repository-DZyJv5pV.js +265 -0
- package/package.json +1 -1
- package/dist/adapter-registry-k7ZX3Bz6.js +0 -4
- package/dist/health-CJqd1FzY.js +0 -6
- package/dist/run-Z_-caE_i.js +0 -9
- package/dist/version-manager-impl-DaA_ALYK.js +0 -4
- /package/dist/{decisions-C0pz9Clx.js → decisions-CzSIEeGP.js} +0 -0
- /package/dist/{routing-CcBOCuC9.js → routing-DFxoKHDt.js} +0 -0
- /package/dist/{version-manager-impl-BmOWu8ml.js → version-manager-impl-qFBiO4Eh.js} +0 -0
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import "./dist-
|
|
2
|
-
import "./version-manager-impl-
|
|
3
|
-
import { isGlobalInstall, registerUpgradeCommand, runUpgradeCommand } from "./upgrade-
|
|
1
|
+
import "./dist-K_RRWnBX.js";
|
|
2
|
+
import "./version-manager-impl-qFBiO4Eh.js";
|
|
3
|
+
import { isGlobalInstall, registerUpgradeCommand, runUpgradeCommand } from "./upgrade-MP9XzrI6.js";
|
|
4
4
|
|
|
5
5
|
export { isGlobalInstall, registerUpgradeCommand, runUpgradeCommand };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { createVersionManager } from "./dist-
|
|
1
|
+
import { createVersionManager } from "./dist-K_RRWnBX.js";
|
|
2
2
|
import { execSync, spawn } from "child_process";
|
|
3
3
|
import * as readline from "readline";
|
|
4
4
|
|
|
@@ -123,4 +123,4 @@ function registerUpgradeCommand(program) {
|
|
|
123
123
|
|
|
124
124
|
//#endregion
|
|
125
125
|
export { isGlobalInstall, registerUpgradeCommand, runUpgradeCommand };
|
|
126
|
-
//# sourceMappingURL=upgrade-
|
|
126
|
+
//# sourceMappingURL=upgrade-MP9XzrI6.js.map
|
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
//#region src/modules/work-graph/cycle-detector.ts
|
|
2
|
+
/**
|
|
3
|
+
* detectCycles — DFS-based cycle detection for story dependency graphs.
|
|
4
|
+
*
|
|
5
|
+
* Story 31-7: Cycle Detection in Work Graph
|
|
6
|
+
*
|
|
7
|
+
* Pure function; no database or I/O dependencies.
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* Detect cycles in a directed dependency graph represented as an edge list.
|
|
11
|
+
*
|
|
12
|
+
* Each edge `{ story_key, depends_on }` means story_key depends on depends_on
|
|
13
|
+
* (i.e. story_key → depends_on is the directed edge we traverse).
|
|
14
|
+
*
|
|
15
|
+
* Uses iterative DFS with an explicit stack to avoid call-stack overflows on
|
|
16
|
+
* large graphs, but also supports a nested recursive helper for cycle path
|
|
17
|
+
* reconstruction.
|
|
18
|
+
*
|
|
19
|
+
* @param edges - List of dependency edges to check.
|
|
20
|
+
* @returns `null` if the graph is acyclic (safe to persist), or a `string[]`
|
|
21
|
+
* containing the cycle path with the first and last element being the same
|
|
22
|
+
* story key (e.g. `['A', 'B', 'A']`).
|
|
23
|
+
*/
|
|
24
|
+
function detectCycles(edges) {
|
|
25
|
+
const adj = new Map();
|
|
26
|
+
for (const { story_key, depends_on } of edges) {
|
|
27
|
+
if (!adj.has(story_key)) adj.set(story_key, []);
|
|
28
|
+
adj.get(story_key).push(depends_on);
|
|
29
|
+
}
|
|
30
|
+
const visited = new Set();
|
|
31
|
+
const visiting = new Set();
|
|
32
|
+
const path = [];
|
|
33
|
+
function dfs(node) {
|
|
34
|
+
if (visiting.has(node)) {
|
|
35
|
+
const cycleStart = path.indexOf(node);
|
|
36
|
+
return [...path.slice(cycleStart), node];
|
|
37
|
+
}
|
|
38
|
+
if (visited.has(node)) return null;
|
|
39
|
+
visiting.add(node);
|
|
40
|
+
path.push(node);
|
|
41
|
+
for (const neighbor of adj.get(node) ?? []) {
|
|
42
|
+
const cycle = dfs(neighbor);
|
|
43
|
+
if (cycle !== null) return cycle;
|
|
44
|
+
}
|
|
45
|
+
path.pop();
|
|
46
|
+
visiting.delete(node);
|
|
47
|
+
visited.add(node);
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
const allNodes = new Set([...edges.map((e) => e.story_key), ...edges.map((e) => e.depends_on)]);
|
|
51
|
+
for (const node of allNodes) if (!visited.has(node)) {
|
|
52
|
+
const cycle = dfs(node);
|
|
53
|
+
if (cycle !== null) return cycle;
|
|
54
|
+
}
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
//#endregion
|
|
59
|
+
//#region src/modules/state/work-graph-repository.ts
|
|
60
|
+
var WorkGraphRepository = class {
|
|
61
|
+
constructor(db) {
|
|
62
|
+
this.db = db;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Insert or replace a work-graph story node.
|
|
66
|
+
* Uses DELETE + INSERT so it works on InMemoryDatabaseAdapter (which does
|
|
67
|
+
* not support ON DUPLICATE KEY UPDATE).
|
|
68
|
+
*/
|
|
69
|
+
async upsertStory(story) {
|
|
70
|
+
await this.db.query(`DELETE FROM wg_stories WHERE story_key = ?`, [story.story_key]);
|
|
71
|
+
await this.db.query(`INSERT INTO wg_stories (story_key, epic, title, status, spec_path, created_at, updated_at, completed_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
72
|
+
story.story_key,
|
|
73
|
+
story.epic,
|
|
74
|
+
story.title ?? null,
|
|
75
|
+
story.status,
|
|
76
|
+
story.spec_path ?? null,
|
|
77
|
+
story.created_at ?? null,
|
|
78
|
+
story.updated_at ?? null,
|
|
79
|
+
story.completed_at ?? null
|
|
80
|
+
]);
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Insert a dependency edge. Idempotent — if a row with the same
|
|
84
|
+
* (story_key, depends_on) already exists it is silently skipped.
|
|
85
|
+
*/
|
|
86
|
+
async addDependency(dep) {
|
|
87
|
+
const existing = await this.db.query(`SELECT story_key FROM story_dependencies WHERE story_key = ? AND depends_on = ?`, [dep.story_key, dep.depends_on]);
|
|
88
|
+
if (existing.length > 0) return;
|
|
89
|
+
await this.db.query(`INSERT INTO story_dependencies (story_key, depends_on, dependency_type, source, created_at) VALUES (?, ?, ?, ?, ?)`, [
|
|
90
|
+
dep.story_key,
|
|
91
|
+
dep.depends_on,
|
|
92
|
+
dep.dependency_type,
|
|
93
|
+
dep.source,
|
|
94
|
+
dep.created_at ?? null
|
|
95
|
+
]);
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Persist contract-based dependency edges to `story_dependencies` as
|
|
99
|
+
* best-effort, idempotent writes.
|
|
100
|
+
*
|
|
101
|
+
* - edges where `reason` does NOT start with `'dual export:'` are persisted
|
|
102
|
+
* as `dependency_type = 'blocks'` (hard prerequisites).
|
|
103
|
+
* - edges where `reason` starts with `'dual export:'` are persisted as
|
|
104
|
+
* `dependency_type = 'informs'` (serialization hints, not hard gates).
|
|
105
|
+
*
|
|
106
|
+
* Idempotency is delegated to `addDependency()`, which skips the INSERT if
|
|
107
|
+
* a row with the same `(story_key, depends_on)` already exists.
|
|
108
|
+
*
|
|
109
|
+
* @param edges - Readonly list of contract dependency edges to persist.
|
|
110
|
+
*/
|
|
111
|
+
async addContractDependencies(edges) {
|
|
112
|
+
if (edges.length === 0) return;
|
|
113
|
+
for (const edge of edges) {
|
|
114
|
+
const dependency_type = edge.reason?.startsWith("dual export:") ? "informs" : "blocks";
|
|
115
|
+
await this.addDependency({
|
|
116
|
+
story_key: edge.to,
|
|
117
|
+
depends_on: edge.from,
|
|
118
|
+
dependency_type,
|
|
119
|
+
source: "contract",
|
|
120
|
+
created_at: new Date().toISOString()
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Return all work-graph stories, optionally filtered by epic and/or status.
|
|
126
|
+
*/
|
|
127
|
+
async listStories(filter) {
|
|
128
|
+
if (!filter || !filter.epic && !filter.status) return this.db.query(`SELECT * FROM wg_stories`);
|
|
129
|
+
const conditions = [];
|
|
130
|
+
const params = [];
|
|
131
|
+
if (filter.epic) {
|
|
132
|
+
conditions.push(`epic = ?`);
|
|
133
|
+
params.push(filter.epic);
|
|
134
|
+
}
|
|
135
|
+
if (filter.status) {
|
|
136
|
+
conditions.push(`status = ?`);
|
|
137
|
+
params.push(filter.status);
|
|
138
|
+
}
|
|
139
|
+
const where = conditions.join(" AND ");
|
|
140
|
+
return this.db.query(`SELECT * FROM wg_stories WHERE ${where}`, params);
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Update the `status` (and optionally `completed_at`) of an existing
|
|
144
|
+
* work-graph story.
|
|
145
|
+
*
|
|
146
|
+
* This is a read-modify-write operation: SELECT existing row → build
|
|
147
|
+
* updated WgStory → upsertStory(). If no row exists for `storyKey` the
|
|
148
|
+
* call is a no-op (AC4).
|
|
149
|
+
*
|
|
150
|
+
* @param storyKey - Story identifier, e.g. "31-4"
|
|
151
|
+
* @param status - Target WgStoryStatus value
|
|
152
|
+
* @param opts - Optional `completedAt` ISO string for terminal phases
|
|
153
|
+
*/
|
|
154
|
+
async updateStoryStatus(storyKey, status, opts) {
|
|
155
|
+
const rows = await this.db.query(`SELECT * FROM wg_stories WHERE story_key = ?`, [storyKey]);
|
|
156
|
+
if (rows.length === 0) return;
|
|
157
|
+
const existing = rows[0];
|
|
158
|
+
const now = new Date().toISOString();
|
|
159
|
+
const isTerminal = status === "complete" || status === "escalated";
|
|
160
|
+
const updated = {
|
|
161
|
+
...existing,
|
|
162
|
+
status,
|
|
163
|
+
updated_at: now,
|
|
164
|
+
completed_at: isTerminal ? opts?.completedAt ?? now : existing.completed_at
|
|
165
|
+
};
|
|
166
|
+
await this.upsertStory(updated);
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Return stories that are eligible for dispatch.
|
|
170
|
+
*
|
|
171
|
+
* A story is ready when:
|
|
172
|
+
* 1. Its status is 'planned' or 'ready', AND
|
|
173
|
+
* 2. It has no 'blocks' dependency whose blocking story is not 'complete'.
|
|
174
|
+
*
|
|
175
|
+
* Soft ('informs') dependencies never block dispatch.
|
|
176
|
+
*
|
|
177
|
+
* This is implemented programmatically rather than via the `ready_stories`
|
|
178
|
+
* VIEW so that the InMemoryDatabaseAdapter can handle it without VIEW support.
|
|
179
|
+
*/
|
|
180
|
+
async getReadyStories() {
|
|
181
|
+
const allStories = await this.db.query(`SELECT * FROM wg_stories`);
|
|
182
|
+
const candidates = allStories.filter((s) => s.status === "planned" || s.status === "ready");
|
|
183
|
+
if (candidates.length === 0) return [];
|
|
184
|
+
const deps = await this.db.query(`SELECT story_key, depends_on FROM story_dependencies WHERE dependency_type = 'blocks'`);
|
|
185
|
+
if (deps.length === 0) return candidates;
|
|
186
|
+
const blockerStatus = new Map(allStories.map((s) => [s.story_key, s.status]));
|
|
187
|
+
const depsMap = new Map();
|
|
188
|
+
for (const d of deps) {
|
|
189
|
+
if (!depsMap.has(d.story_key)) depsMap.set(d.story_key, []);
|
|
190
|
+
depsMap.get(d.story_key).push(d.depends_on);
|
|
191
|
+
}
|
|
192
|
+
return candidates.filter((s) => {
|
|
193
|
+
const blocking = depsMap.get(s.story_key) ?? [];
|
|
194
|
+
return blocking.every((dep) => blockerStatus.get(dep) === "complete");
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Return stories that are planned/ready but cannot be dispatched because
|
|
199
|
+
* at least one hard-blocking ('blocks') dependency is not yet complete.
|
|
200
|
+
*
|
|
201
|
+
* For each blocked story, the returned object includes the full WgStory
|
|
202
|
+
* record plus the list of unsatisfied blockers (key, title, status).
|
|
203
|
+
*
|
|
204
|
+
* Soft ('informs') dependencies are ignored here, matching getReadyStories().
|
|
205
|
+
*/
|
|
206
|
+
/**
|
|
207
|
+
* Query the database for all 'blocks' dependency rows and run DFS cycle
|
|
208
|
+
* detection over them.
|
|
209
|
+
*
|
|
210
|
+
* Returns an empty array if no cycle is found (consistent with other
|
|
211
|
+
* repository methods that return empty arrays rather than null).
|
|
212
|
+
*
|
|
213
|
+
* Only 'blocks' deps are checked — soft 'informs' deps cannot cause
|
|
214
|
+
* dispatch deadlocks (AC5).
|
|
215
|
+
*/
|
|
216
|
+
async detectCycles() {
|
|
217
|
+
const rows = await this.db.query(`SELECT story_key, depends_on FROM story_dependencies WHERE dependency_type = 'blocks'`);
|
|
218
|
+
const cycle = detectCycles(rows);
|
|
219
|
+
return cycle ?? [];
|
|
220
|
+
}
|
|
221
|
+
async getBlockedStories() {
|
|
222
|
+
const allStories = await this.db.query(`SELECT * FROM wg_stories`);
|
|
223
|
+
const candidates = allStories.filter((s) => s.status === "planned" || s.status === "ready");
|
|
224
|
+
if (candidates.length === 0) return [];
|
|
225
|
+
const deps = await this.db.query(`SELECT story_key, depends_on FROM story_dependencies WHERE dependency_type = 'blocks'`);
|
|
226
|
+
if (deps.length === 0) return [];
|
|
227
|
+
const statusMap = new Map(allStories.map((s) => [s.story_key, s]));
|
|
228
|
+
const depsMap = new Map();
|
|
229
|
+
for (const d of deps) {
|
|
230
|
+
if (!depsMap.has(d.story_key)) depsMap.set(d.story_key, []);
|
|
231
|
+
depsMap.get(d.story_key).push(d.depends_on);
|
|
232
|
+
}
|
|
233
|
+
const result = [];
|
|
234
|
+
for (const story of candidates) {
|
|
235
|
+
const blockerKeys = depsMap.get(story.story_key) ?? [];
|
|
236
|
+
const unsatisfied = blockerKeys.filter((key) => statusMap.get(key)?.status !== "complete").map((key) => {
|
|
237
|
+
const s = statusMap.get(key);
|
|
238
|
+
return {
|
|
239
|
+
key,
|
|
240
|
+
title: s?.title ?? key,
|
|
241
|
+
status: s?.status ?? "unknown"
|
|
242
|
+
};
|
|
243
|
+
});
|
|
244
|
+
if (unsatisfied.length > 0) result.push({
|
|
245
|
+
story,
|
|
246
|
+
blockers: unsatisfied
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
return result;
|
|
250
|
+
}
|
|
251
|
+
/**
|
|
252
|
+
* Return all 'blocks' dependency edges from story_dependencies.
|
|
253
|
+
*
|
|
254
|
+
* Used by back-pressure logic (e.g., Recovery Engine computeDependencyAwarePause)
|
|
255
|
+
* to find which pending stories depend on proposed/blocked stories without
|
|
256
|
+
* requiring a status-filtered candidate query like getBlockedStories().
|
|
257
|
+
*/
|
|
258
|
+
async getDependencyEdges() {
|
|
259
|
+
return this.db.query(`SELECT story_key, depends_on FROM story_dependencies WHERE dependency_type = 'blocks'`);
|
|
260
|
+
}
|
|
261
|
+
};
|
|
262
|
+
|
|
263
|
+
//#endregion
|
|
264
|
+
export { WorkGraphRepository, detectCycles };
|
|
265
|
+
//# sourceMappingURL=work-graph-repository-DZyJv5pV.js.map
|
package/package.json
CHANGED
package/dist/health-CJqd1FzY.js
DELETED
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
import { DEFAULT_STALL_THRESHOLD_SECONDS, getAllDescendantPids, getAutoHealthData, inspectProcessTree, isOrchestratorProcessLine, registerHealthCommand, runHealthAction } from "./health-C-KOZrFJ.js";
|
|
2
|
-
import "./logger-KeHncl-f.js";
|
|
3
|
-
import "./dist-W2emvN3F.js";
|
|
4
|
-
import "./decisions-C0pz9Clx.js";
|
|
5
|
-
|
|
6
|
-
export { inspectProcessTree };
|
package/dist/run-Z_-caE_i.js
DELETED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import "./health-C-KOZrFJ.js";
|
|
2
|
-
import "./logger-KeHncl-f.js";
|
|
3
|
-
import "./helpers-CElYrONe.js";
|
|
4
|
-
import "./dist-W2emvN3F.js";
|
|
5
|
-
import { normalizeGraphSummaryToStatus, registerRunCommand, resolveMaxReviewCycles, resolveProbeAuthorStateIntegrating, runRunAction, wireNdjsonEmitter } from "./run-CHUFlRbH.js";
|
|
6
|
-
import "./routing-CcBOCuC9.js";
|
|
7
|
-
import "./decisions-C0pz9Clx.js";
|
|
8
|
-
|
|
9
|
-
export { runRunAction };
|
|
File without changes
|
|
File without changes
|
|
File without changes
|