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.
Files changed (36) hide show
  1. package/dist/adapter-registry-BbVWH3Yv.js +4 -0
  2. package/dist/cli/index.js +93 -23
  3. package/dist/decision-router-DblHY8se.js +97 -0
  4. package/dist/{decisions-4F91LrVD.js → decisions-DilHo99V.js} +2 -2
  5. package/dist/{dist-W2emvN3F.js → dist-K_RRWnBX.js} +2 -2
  6. package/dist/{errors-CKFu8YI9.js → errors-pSiZbn6e.js} +2 -2
  7. package/dist/{experimenter-DxxwicpK.js → experimenter-DT9v2Pto.js} +1 -1
  8. package/dist/health-DC3y-sR6.js +1715 -0
  9. package/dist/health-qhtWYh49.js +8 -0
  10. package/dist/index-c924O9mj.d.ts +1432 -0
  11. package/dist/index.d.ts +132 -736
  12. package/dist/index.js +2 -2
  13. package/dist/interactive-prompt-C7wpE4z4.js +183 -0
  14. package/dist/{health-C-KOZrFJ.js → manifest-read-DDkXC3L_.js} +130 -2013
  15. package/dist/modules/interactive-prompt/index.d.ts +86 -0
  16. package/dist/modules/interactive-prompt/index.js +6 -0
  17. package/dist/recovery-engine-BKGBeBnW.js +281 -0
  18. package/dist/{routing-0ykvBl_4.js → routing-CzF0p6lI.js} +2 -2
  19. package/dist/run-DX95j4_D.js +14 -0
  20. package/dist/{run-CHUFlRbH.js → run-DzB4rgkj.js} +391 -31
  21. package/dist/src/modules/decision-router/index.d.ts +88 -0
  22. package/dist/src/modules/decision-router/index.js +3 -0
  23. package/dist/src/modules/recovery-engine/index.d.ts +1101 -0
  24. package/dist/src/modules/recovery-engine/index.js +5 -0
  25. package/dist/{upgrade-C8LAnB6W.js → upgrade-DxzQ1nss.js} +3 -3
  26. package/dist/{upgrade-CAqLkNUP.js → upgrade-MP9XzrI6.js} +2 -2
  27. package/dist/version-manager-impl-GZDUBt0Q.js +4 -0
  28. package/dist/work-graph-repository-DZyJv5pV.js +265 -0
  29. package/package.json +1 -1
  30. package/dist/adapter-registry-k7ZX3Bz6.js +0 -4
  31. package/dist/health-CJqd1FzY.js +0 -6
  32. package/dist/run-Z_-caE_i.js +0 -9
  33. package/dist/version-manager-impl-DaA_ALYK.js +0 -4
  34. /package/dist/{decisions-C0pz9Clx.js → decisions-CzSIEeGP.js} +0 -0
  35. /package/dist/{routing-CcBOCuC9.js → routing-DFxoKHDt.js} +0 -0
  36. /package/dist/{version-manager-impl-BmOWu8ml.js → version-manager-impl-qFBiO4Eh.js} +0 -0
@@ -0,0 +1,5 @@
1
+ import "../../../logger-KeHncl-f.js";
2
+ import "../../../work-graph-repository-DZyJv5pV.js";
3
+ import { classifyRecoveryAction, runRecoveryEngine } from "../../../recovery-engine-BKGBeBnW.js";
4
+
5
+ export { classifyRecoveryAction, runRecoveryEngine };
@@ -1,5 +1,5 @@
1
- import "./dist-W2emvN3F.js";
2
- import "./version-manager-impl-BmOWu8ml.js";
3
- import { isGlobalInstall, registerUpgradeCommand, runUpgradeCommand } from "./upgrade-CAqLkNUP.js";
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-W2emvN3F.js";
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-CAqLkNUP.js.map
126
+ //# sourceMappingURL=upgrade-MP9XzrI6.js.map
@@ -0,0 +1,4 @@
1
+ import { VersionManagerImpl, createVersionManager } from "./dist-K_RRWnBX.js";
2
+ import "./version-manager-impl-qFBiO4Eh.js";
3
+
4
+ export { createVersionManager };
@@ -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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "substrate-ai",
3
- "version": "0.20.63",
3
+ "version": "0.20.65",
4
4
  "description": "Substrate — multi-agent orchestration daemon for AI coding agents",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -1,4 +0,0 @@
1
- import { AdapterRegistry } from "./dist-W2emvN3F.js";
2
- import "./adapter-registry-DXLMTmfD.js";
3
-
4
- export { AdapterRegistry };
@@ -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 };
@@ -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 };
@@ -1,4 +0,0 @@
1
- import { VersionManagerImpl, createVersionManager } from "./dist-W2emvN3F.js";
2
- import "./version-manager-impl-BmOWu8ml.js";
3
-
4
- export { createVersionManager };