gsd-pi 2.37.1 → 2.38.0-dev.add4f78
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/dist/app-paths.js +1 -1
- package/dist/cli.js +9 -0
- package/dist/extension-discovery.d.ts +5 -3
- package/dist/extension-discovery.js +14 -9
- package/dist/extension-registry.js +2 -2
- package/dist/onboarding.js +1 -0
- package/dist/remote-questions-config.js +2 -2
- package/dist/resources/extensions/browser-tools/package.json +3 -1
- package/dist/resources/extensions/cmux/index.js +55 -1
- package/dist/resources/extensions/context7/package.json +1 -1
- package/dist/resources/extensions/env-utils.js +29 -0
- package/dist/resources/extensions/get-secrets-from-user.js +5 -24
- package/dist/resources/extensions/google-search/package.json +3 -1
- package/dist/resources/extensions/gsd/auto/session.js +3 -0
- package/dist/resources/extensions/gsd/auto-dispatch.js +74 -9
- package/dist/resources/extensions/gsd/auto-loop.js +61 -31
- package/dist/resources/extensions/gsd/auto-post-unit.js +87 -69
- package/dist/resources/extensions/gsd/auto-prompts.js +91 -2
- package/dist/resources/extensions/gsd/auto-recovery.js +37 -1
- package/dist/resources/extensions/gsd/auto-start.js +6 -1
- package/dist/resources/extensions/gsd/auto-worktree-sync.js +13 -5
- package/dist/resources/extensions/gsd/auto.js +10 -26
- package/dist/resources/extensions/gsd/captures.js +9 -1
- package/dist/resources/extensions/gsd/commands-extensions.js +3 -2
- package/dist/resources/extensions/gsd/commands-handlers.js +16 -3
- package/dist/resources/extensions/gsd/commands.js +22 -2
- package/dist/resources/extensions/gsd/detection.js +1 -2
- package/dist/resources/extensions/gsd/doctor-checks.js +82 -0
- package/dist/resources/extensions/gsd/doctor-environment.js +78 -0
- package/dist/resources/extensions/gsd/doctor-format.js +15 -0
- package/dist/resources/extensions/gsd/doctor-providers.js +35 -1
- package/dist/resources/extensions/gsd/doctor.js +184 -11
- package/dist/resources/extensions/gsd/export.js +1 -1
- package/dist/resources/extensions/gsd/files.js +43 -2
- package/dist/resources/extensions/gsd/forensics.js +1 -1
- package/dist/resources/extensions/gsd/index.js +2 -1
- package/dist/resources/extensions/gsd/migrate/parsers.js +1 -1
- package/dist/resources/extensions/gsd/observability-validator.js +24 -0
- package/dist/resources/extensions/gsd/package.json +1 -1
- package/dist/resources/extensions/gsd/preferences-types.js +2 -1
- package/dist/resources/extensions/gsd/preferences-validation.js +43 -1
- package/dist/resources/extensions/gsd/preferences.js +4 -3
- package/dist/resources/extensions/gsd/prompts/plan-slice.md +2 -1
- package/dist/resources/extensions/gsd/prompts/reactive-execute.md +41 -0
- package/dist/resources/extensions/gsd/reactive-graph.js +227 -0
- package/dist/resources/extensions/gsd/repo-identity.js +2 -1
- package/dist/resources/extensions/gsd/resource-version.js +2 -1
- package/dist/resources/extensions/gsd/state.js +1 -1
- package/dist/resources/extensions/gsd/templates/task-plan.md +11 -3
- package/dist/resources/extensions/gsd/visualizer-data.js +1 -1
- package/dist/resources/extensions/gsd/worktree.js +35 -16
- package/dist/resources/extensions/remote-questions/status.js +2 -1
- package/dist/resources/extensions/remote-questions/store.js +2 -1
- package/dist/resources/extensions/search-the-web/provider.js +2 -1
- package/dist/resources/extensions/subagent/index.js +12 -3
- package/dist/resources/extensions/subagent/isolation.js +2 -1
- package/dist/resources/extensions/ttsr/rule-loader.js +2 -1
- package/dist/resources/extensions/universal-config/package.json +1 -1
- package/dist/welcome-screen.d.ts +12 -0
- package/dist/welcome-screen.js +53 -0
- package/package.json +2 -1
- package/packages/pi-ai/dist/env-api-keys.js +13 -0
- package/packages/pi-ai/dist/env-api-keys.js.map +1 -1
- package/packages/pi-ai/dist/models.generated.d.ts +172 -0
- package/packages/pi-ai/dist/models.generated.d.ts.map +1 -1
- package/packages/pi-ai/dist/models.generated.js +172 -0
- package/packages/pi-ai/dist/models.generated.js.map +1 -1
- package/packages/pi-ai/dist/providers/anthropic-shared.d.ts +64 -0
- package/packages/pi-ai/dist/providers/anthropic-shared.d.ts.map +1 -0
- package/packages/pi-ai/dist/providers/anthropic-shared.js +668 -0
- package/packages/pi-ai/dist/providers/anthropic-shared.js.map +1 -0
- package/packages/pi-ai/dist/providers/anthropic-vertex.d.ts +5 -0
- package/packages/pi-ai/dist/providers/anthropic-vertex.d.ts.map +1 -0
- package/packages/pi-ai/dist/providers/anthropic-vertex.js +85 -0
- package/packages/pi-ai/dist/providers/anthropic-vertex.js.map +1 -0
- package/packages/pi-ai/dist/providers/anthropic.d.ts +4 -30
- package/packages/pi-ai/dist/providers/anthropic.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/anthropic.js +47 -764
- package/packages/pi-ai/dist/providers/anthropic.js.map +1 -1
- package/packages/pi-ai/dist/providers/register-builtins.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/register-builtins.js +6 -0
- package/packages/pi-ai/dist/providers/register-builtins.js.map +1 -1
- package/packages/pi-ai/dist/types.d.ts +2 -2
- package/packages/pi-ai/dist/types.d.ts.map +1 -1
- package/packages/pi-ai/dist/types.js.map +1 -1
- package/packages/pi-ai/package.json +1 -0
- package/packages/pi-ai/src/env-api-keys.ts +14 -0
- package/packages/pi-ai/src/models.generated.ts +172 -0
- package/packages/pi-ai/src/providers/anthropic-shared.ts +761 -0
- package/packages/pi-ai/src/providers/anthropic-vertex.ts +130 -0
- package/packages/pi-ai/src/providers/anthropic.ts +76 -868
- package/packages/pi-ai/src/providers/register-builtins.ts +7 -0
- package/packages/pi-ai/src/types.ts +2 -0
- package/packages/pi-coding-agent/dist/core/model-resolver.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/model-resolver.js +1 -0
- package/packages/pi-coding-agent/dist/core/model-resolver.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/package-manager.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/package-manager.js +8 -4
- package/packages/pi-coding-agent/dist/core/package-manager.js.map +1 -1
- package/packages/pi-coding-agent/package.json +1 -1
- package/packages/pi-coding-agent/src/core/model-resolver.ts +1 -0
- package/packages/pi-coding-agent/src/core/package-manager.ts +8 -4
- package/pkg/package.json +1 -1
- package/src/resources/extensions/cmux/index.ts +57 -1
- package/src/resources/extensions/env-utils.ts +31 -0
- package/src/resources/extensions/get-secrets-from-user.ts +5 -24
- package/src/resources/extensions/gsd/auto/session.ts +5 -1
- package/src/resources/extensions/gsd/auto-dispatch.ts +99 -8
- package/src/resources/extensions/gsd/auto-loop.ts +83 -64
- package/src/resources/extensions/gsd/auto-post-unit.ts +64 -40
- package/src/resources/extensions/gsd/auto-prompts.ts +125 -3
- package/src/resources/extensions/gsd/auto-recovery.ts +42 -0
- package/src/resources/extensions/gsd/auto-start.ts +7 -1
- package/src/resources/extensions/gsd/auto-worktree-sync.ts +15 -4
- package/src/resources/extensions/gsd/auto.ts +14 -29
- package/src/resources/extensions/gsd/captures.ts +10 -1
- package/src/resources/extensions/gsd/commands-extensions.ts +4 -2
- package/src/resources/extensions/gsd/commands-handlers.ts +17 -2
- package/src/resources/extensions/gsd/commands.ts +24 -2
- package/src/resources/extensions/gsd/detection.ts +2 -2
- package/src/resources/extensions/gsd/doctor-checks.ts +75 -0
- package/src/resources/extensions/gsd/doctor-environment.ts +82 -1
- package/src/resources/extensions/gsd/doctor-format.ts +20 -0
- package/src/resources/extensions/gsd/doctor-providers.ts +38 -1
- package/src/resources/extensions/gsd/doctor-types.ts +16 -1
- package/src/resources/extensions/gsd/doctor.ts +177 -13
- package/src/resources/extensions/gsd/export.ts +1 -1
- package/src/resources/extensions/gsd/files.ts +47 -2
- package/src/resources/extensions/gsd/forensics.ts +1 -1
- package/src/resources/extensions/gsd/index.ts +3 -1
- package/src/resources/extensions/gsd/migrate/parsers.ts +1 -1
- package/src/resources/extensions/gsd/observability-validator.ts +27 -0
- package/src/resources/extensions/gsd/preferences-types.ts +5 -1
- package/src/resources/extensions/gsd/preferences-validation.ts +42 -1
- package/src/resources/extensions/gsd/preferences.ts +5 -3
- package/src/resources/extensions/gsd/prompts/plan-slice.md +2 -1
- package/src/resources/extensions/gsd/prompts/reactive-execute.md +41 -0
- package/src/resources/extensions/gsd/reactive-graph.ts +289 -0
- package/src/resources/extensions/gsd/repo-identity.ts +3 -1
- package/src/resources/extensions/gsd/resource-version.ts +3 -1
- package/src/resources/extensions/gsd/state.ts +1 -1
- package/src/resources/extensions/gsd/templates/task-plan.md +11 -3
- package/src/resources/extensions/gsd/tests/cmux.test.ts +93 -0
- package/src/resources/extensions/gsd/tests/doctor-enhancements.test.ts +266 -0
- package/src/resources/extensions/gsd/tests/doctor-providers.test.ts +108 -3
- package/src/resources/extensions/gsd/tests/plan-quality-validator.test.ts +111 -0
- package/src/resources/extensions/gsd/tests/reactive-executor.test.ts +511 -0
- package/src/resources/extensions/gsd/tests/reactive-graph.test.ts +299 -0
- package/src/resources/extensions/gsd/tests/worktree.test.ts +47 -0
- package/src/resources/extensions/gsd/types.ts +43 -0
- package/src/resources/extensions/gsd/visualizer-data.ts +1 -1
- package/src/resources/extensions/gsd/worktree.ts +35 -15
- package/src/resources/extensions/remote-questions/status.ts +3 -1
- package/src/resources/extensions/remote-questions/store.ts +3 -1
- package/src/resources/extensions/search-the-web/provider.ts +2 -1
- package/src/resources/extensions/subagent/index.ts +12 -3
- package/src/resources/extensions/subagent/isolation.ts +3 -1
- package/src/resources/extensions/ttsr/rule-loader.ts +3 -1
|
@@ -22,7 +22,6 @@ import { writeUnitRuntimeRecord, clearUnitRuntimeRecord } from "./unit-runtime.j
|
|
|
22
22
|
import { runGSDDoctor, rebuildState, summarizeDoctorIssues } from "./doctor.js";
|
|
23
23
|
import { recordHealthSnapshot, checkHealEscalation } from "./doctor-proactive.js";
|
|
24
24
|
import { syncStateToProjectRoot } from "./auto-worktree-sync.js";
|
|
25
|
-
import { resetRewriteCircuitBreaker } from "./auto-dispatch.js";
|
|
26
25
|
import { isDbAvailable } from "./gsd-db.js";
|
|
27
26
|
import { consumeSignal } from "./session-status-io.js";
|
|
28
27
|
import { checkPostUnitHooks, isRetryPending, consumeRetryTrigger, persistHookState, } from "./post-unit-hooks.js";
|
|
@@ -36,7 +35,7 @@ const STATE_REBUILD_MIN_INTERVAL_MS = 30_000;
|
|
|
36
35
|
*
|
|
37
36
|
* Returns "dispatched" if a signal caused stop/pause, "continue" to proceed.
|
|
38
37
|
*/
|
|
39
|
-
export async function postUnitPreVerification(pctx) {
|
|
38
|
+
export async function postUnitPreVerification(pctx, opts) {
|
|
40
39
|
const { s, ctx, pi, buildSnapshotOpts, stopAuto, pauseAuto } = pctx;
|
|
41
40
|
// ── Parallel worker signal check ──
|
|
42
41
|
const milestoneLock = process.env.GSD_MILESTONE_LOCK;
|
|
@@ -55,8 +54,10 @@ export async function postUnitPreVerification(pctx) {
|
|
|
55
54
|
}
|
|
56
55
|
// Invalidate all caches
|
|
57
56
|
invalidateAllCaches();
|
|
58
|
-
// Small delay to let files settle
|
|
59
|
-
|
|
57
|
+
// Small delay to let files settle (skipped for sidecars where latency matters more)
|
|
58
|
+
if (!opts?.skipSettleDelay) {
|
|
59
|
+
await new Promise(r => setTimeout(r, 100));
|
|
60
|
+
}
|
|
60
61
|
// Auto-commit
|
|
61
62
|
if (s.currentUnit) {
|
|
62
63
|
try {
|
|
@@ -79,8 +80,8 @@ export async function postUnitPreVerification(pctx) {
|
|
|
79
80
|
};
|
|
80
81
|
}
|
|
81
82
|
}
|
|
82
|
-
catch {
|
|
83
|
-
|
|
83
|
+
catch (e) {
|
|
84
|
+
debugLog("postUnit", { phase: "task-summary-parse", error: String(e) });
|
|
84
85
|
}
|
|
85
86
|
}
|
|
86
87
|
}
|
|
@@ -90,57 +91,60 @@ export async function postUnitPreVerification(pctx) {
|
|
|
90
91
|
ctx.ui.notify(`Committed: ${commitMsg.split("\n")[0]}`, "info");
|
|
91
92
|
}
|
|
92
93
|
}
|
|
93
|
-
catch {
|
|
94
|
-
|
|
94
|
+
catch (e) {
|
|
95
|
+
debugLog("postUnit", { phase: "auto-commit", error: String(e) });
|
|
95
96
|
}
|
|
96
|
-
// Doctor: fix mechanical bookkeeping
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
97
|
+
// Doctor: fix mechanical bookkeeping (skipped for lightweight sidecars)
|
|
98
|
+
if (!opts?.skipDoctor)
|
|
99
|
+
try {
|
|
100
|
+
const scopeParts = s.currentUnit.id.split("/").slice(0, 2);
|
|
101
|
+
const doctorScope = scopeParts.join("/");
|
|
102
|
+
const sliceTerminalUnits = new Set(["complete-slice", "run-uat"]);
|
|
103
|
+
const effectiveFixLevel = sliceTerminalUnits.has(s.currentUnit.type) ? "all" : "task";
|
|
104
|
+
const report = await runGSDDoctor(s.basePath, { fix: true, scope: doctorScope, fixLevel: effectiveFixLevel });
|
|
105
|
+
if (report.fixesApplied.length > 0) {
|
|
106
|
+
ctx.ui.notify(`Post-hook: applied ${report.fixesApplied.length} fix(es).`, "info");
|
|
107
|
+
}
|
|
108
|
+
// Proactive health tracking
|
|
109
|
+
const summary = summarizeDoctorIssues(report.issues);
|
|
110
|
+
recordHealthSnapshot(summary.errors, summary.warnings, report.fixesApplied.length);
|
|
111
|
+
// Check if we should escalate to LLM-assisted heal
|
|
112
|
+
if (summary.errors > 0) {
|
|
113
|
+
const unresolvedErrors = report.issues
|
|
114
|
+
.filter(i => i.severity === "error" && !i.fixable)
|
|
115
|
+
.map(i => ({ code: i.code, message: i.message, unitId: i.unitId }));
|
|
116
|
+
const escalation = checkHealEscalation(summary.errors, unresolvedErrors);
|
|
117
|
+
if (escalation.shouldEscalate) {
|
|
118
|
+
ctx.ui.notify(`Doctor heal escalation: ${escalation.reason}. Dispatching LLM-assisted heal.`, "warning");
|
|
119
|
+
try {
|
|
120
|
+
const { formatDoctorIssuesForPrompt, formatDoctorReport } = await import("./doctor.js");
|
|
121
|
+
const { dispatchDoctorHeal } = await import("./commands-handlers.js");
|
|
122
|
+
const actionable = report.issues.filter(i => i.severity === "error");
|
|
123
|
+
const reportText = formatDoctorReport(report, { scope: doctorScope, includeWarnings: true });
|
|
124
|
+
const structuredIssues = formatDoctorIssuesForPrompt(actionable);
|
|
125
|
+
dispatchDoctorHeal(pi, doctorScope, reportText, structuredIssues);
|
|
126
|
+
}
|
|
127
|
+
catch (e) {
|
|
128
|
+
debugLog("postUnit", { phase: "doctor-heal-dispatch", error: String(e) });
|
|
129
|
+
}
|
|
127
130
|
}
|
|
128
131
|
}
|
|
129
132
|
}
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
// Non-fatal
|
|
133
|
-
}
|
|
134
|
-
// Throttled STATE.md rebuild
|
|
135
|
-
const now = Date.now();
|
|
136
|
-
if (now - s.lastStateRebuildAt >= STATE_REBUILD_MIN_INTERVAL_MS) {
|
|
137
|
-
try {
|
|
138
|
-
await rebuildState(s.basePath);
|
|
139
|
-
s.lastStateRebuildAt = now;
|
|
140
|
-
autoCommitCurrentBranch(s.basePath, "state-rebuild", s.currentUnit.id);
|
|
133
|
+
catch (e) {
|
|
134
|
+
debugLog("postUnit", { phase: "doctor", error: String(e) });
|
|
141
135
|
}
|
|
142
|
-
|
|
143
|
-
|
|
136
|
+
// Throttled STATE.md rebuild (skipped for lightweight sidecars)
|
|
137
|
+
if (!opts?.skipStateRebuild) {
|
|
138
|
+
const now = Date.now();
|
|
139
|
+
if (now - s.lastStateRebuildAt >= STATE_REBUILD_MIN_INTERVAL_MS) {
|
|
140
|
+
try {
|
|
141
|
+
await rebuildState(s.basePath);
|
|
142
|
+
s.lastStateRebuildAt = now;
|
|
143
|
+
autoCommitCurrentBranch(s.basePath, "state-rebuild", s.currentUnit.id);
|
|
144
|
+
}
|
|
145
|
+
catch (e) {
|
|
146
|
+
debugLog("postUnit", { phase: "state-rebuild", error: String(e) });
|
|
147
|
+
}
|
|
144
148
|
}
|
|
145
149
|
}
|
|
146
150
|
// Prune dead bg-shell processes
|
|
@@ -148,27 +152,41 @@ export async function postUnitPreVerification(pctx) {
|
|
|
148
152
|
const { pruneDeadProcesses } = await import("../bg-shell/process-manager.js");
|
|
149
153
|
pruneDeadProcesses();
|
|
150
154
|
}
|
|
151
|
-
catch {
|
|
152
|
-
|
|
155
|
+
catch (e) {
|
|
156
|
+
debugLog("postUnit", { phase: "prune-bg-shell", error: String(e) });
|
|
153
157
|
}
|
|
154
|
-
// Sync worktree state back to project root
|
|
155
|
-
if (s.originalBasePath && s.originalBasePath !== s.basePath) {
|
|
158
|
+
// Sync worktree state back to project root (skipped for lightweight sidecars)
|
|
159
|
+
if (!opts?.skipWorktreeSync && s.originalBasePath && s.originalBasePath !== s.basePath) {
|
|
156
160
|
try {
|
|
157
161
|
syncStateToProjectRoot(s.basePath, s.originalBasePath, s.currentMilestoneId);
|
|
158
162
|
}
|
|
159
|
-
catch {
|
|
160
|
-
|
|
163
|
+
catch (e) {
|
|
164
|
+
debugLog("postUnit", { phase: "worktree-sync", error: String(e) });
|
|
161
165
|
}
|
|
162
166
|
}
|
|
163
167
|
// Rewrite-docs completion
|
|
164
168
|
if (s.currentUnit.type === "rewrite-docs") {
|
|
165
169
|
try {
|
|
166
170
|
await resolveAllOverrides(s.basePath);
|
|
167
|
-
|
|
171
|
+
s.rewriteAttemptCount = 0;
|
|
168
172
|
ctx.ui.notify("Override(s) resolved — rewrite-docs completed.", "info");
|
|
169
173
|
}
|
|
170
|
-
catch {
|
|
171
|
-
|
|
174
|
+
catch (e) {
|
|
175
|
+
debugLog("postUnit", { phase: "rewrite-docs-resolve", error: String(e) });
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
// Reactive state cleanup on slice completion
|
|
179
|
+
if (s.currentUnit.type === "complete-slice") {
|
|
180
|
+
try {
|
|
181
|
+
const parts = s.currentUnit.id.split("/");
|
|
182
|
+
const [mid, sid] = parts;
|
|
183
|
+
if (mid && sid) {
|
|
184
|
+
const { clearReactiveState } = await import("./reactive-graph.js");
|
|
185
|
+
clearReactiveState(s.basePath, mid, sid);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
catch (e) {
|
|
189
|
+
debugLog("postUnit", { phase: "reactive-state-cleanup", error: String(e) });
|
|
172
190
|
}
|
|
173
191
|
}
|
|
174
192
|
// Post-triage: execute actionable resolutions
|
|
@@ -210,8 +228,8 @@ export async function postUnitPreVerification(pctx) {
|
|
|
210
228
|
invalidateAllCaches();
|
|
211
229
|
}
|
|
212
230
|
}
|
|
213
|
-
catch {
|
|
214
|
-
|
|
231
|
+
catch (e) {
|
|
232
|
+
debugLog("postUnit", { phase: "artifact-verify", error: String(e) });
|
|
215
233
|
}
|
|
216
234
|
}
|
|
217
235
|
else {
|
|
@@ -224,8 +242,8 @@ export async function postUnitPreVerification(pctx) {
|
|
|
224
242
|
});
|
|
225
243
|
clearUnitRuntimeRecord(s.basePath, s.currentUnit.type, s.currentUnit.id);
|
|
226
244
|
}
|
|
227
|
-
catch {
|
|
228
|
-
|
|
245
|
+
catch (e) {
|
|
246
|
+
debugLog("postUnit", { phase: "hook-finalize", error: String(e) });
|
|
229
247
|
}
|
|
230
248
|
}
|
|
231
249
|
}
|
|
@@ -338,8 +356,8 @@ export async function postUnitPostVerification(pctx) {
|
|
|
338
356
|
}
|
|
339
357
|
}
|
|
340
358
|
}
|
|
341
|
-
catch {
|
|
342
|
-
|
|
359
|
+
catch (e) {
|
|
360
|
+
debugLog("postUnit", { phase: "triage-check", error: String(e) });
|
|
343
361
|
}
|
|
344
362
|
}
|
|
345
363
|
// ── Quick-task dispatch ──
|
|
@@ -373,8 +391,8 @@ export async function postUnitPostVerification(pctx) {
|
|
|
373
391
|
ctx.ui.notify(`Executing quick-task: ${capture.id} — "${capture.text}"`, "info");
|
|
374
392
|
return "continue";
|
|
375
393
|
}
|
|
376
|
-
catch {
|
|
377
|
-
|
|
394
|
+
catch (e) {
|
|
395
|
+
debugLog("postUnit", { phase: "quick-task-dispatch", error: String(e) });
|
|
378
396
|
}
|
|
379
397
|
}
|
|
380
398
|
// Step mode → show wizard instead of dispatch
|
|
@@ -414,6 +414,35 @@ export async function getPriorTaskSummaryPaths(mid, sid, currentTid, base) {
|
|
|
414
414
|
})
|
|
415
415
|
.map(f => `${sRel}/tasks/${f}`);
|
|
416
416
|
}
|
|
417
|
+
/**
|
|
418
|
+
* Get carry-forward summary paths scoped to a task's derived dependencies.
|
|
419
|
+
*
|
|
420
|
+
* Instead of all prior tasks (order-based), returns only summaries for task
|
|
421
|
+
* IDs in `dependsOn`. Used by reactive-execute to give each subagent only
|
|
422
|
+
* the context it actually needs — not sibling tasks from a parallel batch.
|
|
423
|
+
*
|
|
424
|
+
* Falls back to order-based when dependsOn is empty (root tasks still get
|
|
425
|
+
* any available prior summaries for continuity).
|
|
426
|
+
*/
|
|
427
|
+
export async function getDependencyTaskSummaryPaths(mid, sid, currentTid, dependsOn, base) {
|
|
428
|
+
// If no dependencies, fall back to order-based for root tasks
|
|
429
|
+
if (dependsOn.length === 0) {
|
|
430
|
+
return getPriorTaskSummaryPaths(mid, sid, currentTid, base);
|
|
431
|
+
}
|
|
432
|
+
const tDir = resolveTasksDir(base, mid, sid);
|
|
433
|
+
if (!tDir)
|
|
434
|
+
return [];
|
|
435
|
+
const summaryFiles = resolveTaskFiles(tDir, "SUMMARY");
|
|
436
|
+
const sRel = relSlicePath(base, mid, sid);
|
|
437
|
+
const depSet = new Set(dependsOn.map((d) => d.toUpperCase()));
|
|
438
|
+
return summaryFiles
|
|
439
|
+
.filter((f) => {
|
|
440
|
+
// Extract task ID from filename: "T02-SUMMARY.md" → "T02"
|
|
441
|
+
const tid = f.replace(/-SUMMARY\.md$/i, "").toUpperCase();
|
|
442
|
+
return depSet.has(tid);
|
|
443
|
+
})
|
|
444
|
+
.map((f) => `${sRel}/tasks/${f}`);
|
|
445
|
+
}
|
|
417
446
|
// ─── Adaptive Replanning Checks ────────────────────────────────────────────
|
|
418
447
|
/**
|
|
419
448
|
* Check if the most recently completed slice needs reassessment.
|
|
@@ -688,8 +717,11 @@ export async function buildPlanSlicePrompt(mid, _midTitle, sid, sTitle, base, le
|
|
|
688
717
|
});
|
|
689
718
|
}
|
|
690
719
|
export async function buildExecuteTaskPrompt(mid, sid, sTitle, tid, tTitle, base, level) {
|
|
691
|
-
const
|
|
692
|
-
|
|
720
|
+
const opts = typeof level === "object" && level !== null && !Array.isArray(level)
|
|
721
|
+
? level
|
|
722
|
+
: { level: level };
|
|
723
|
+
const inlineLevel = opts.level ?? resolveInlineLevel();
|
|
724
|
+
const priorSummaries = opts.carryForwardPaths ?? await getPriorTaskSummaryPaths(mid, sid, tid, base);
|
|
693
725
|
const priorLines = priorSummaries.length > 0
|
|
694
726
|
? priorSummaries.map(p => `- \`${p}\``).join("\n")
|
|
695
727
|
: "- (no prior tasks)";
|
|
@@ -1090,6 +1122,63 @@ export async function buildReassessRoadmapPrompt(mid, midTitle, completedSliceId
|
|
|
1090
1122
|
commitInstruction: reassessCommitInstruction,
|
|
1091
1123
|
});
|
|
1092
1124
|
}
|
|
1125
|
+
// ─── Reactive Execute Prompt ──────────────────────────────────────────────
|
|
1126
|
+
export async function buildReactiveExecutePrompt(mid, midTitle, sid, sTitle, readyTaskIds, base) {
|
|
1127
|
+
const { loadSliceTaskIO, deriveTaskGraph, graphMetrics } = await import("./reactive-graph.js");
|
|
1128
|
+
// Build graph for context
|
|
1129
|
+
const taskIO = await loadSliceTaskIO(base, mid, sid);
|
|
1130
|
+
const graph = deriveTaskGraph(taskIO);
|
|
1131
|
+
const metrics = graphMetrics(graph);
|
|
1132
|
+
// Build graph context section
|
|
1133
|
+
const graphLines = [];
|
|
1134
|
+
for (const node of graph) {
|
|
1135
|
+
const status = node.done ? "✅ done" : readyTaskIds.includes(node.id) ? "🟢 ready" : "⏳ waiting";
|
|
1136
|
+
const deps = node.dependsOn.length > 0 ? ` (depends on: ${node.dependsOn.join(", ")})` : "";
|
|
1137
|
+
graphLines.push(`- **${node.id}: ${node.title}** — ${status}${deps}`);
|
|
1138
|
+
if (node.outputFiles.length > 0) {
|
|
1139
|
+
graphLines.push(` - Outputs: ${node.outputFiles.map(f => `\`${f}\``).join(", ")}`);
|
|
1140
|
+
}
|
|
1141
|
+
}
|
|
1142
|
+
const graphContext = [
|
|
1143
|
+
`Tasks: ${metrics.taskCount}, Edges: ${metrics.edgeCount}, Ready: ${metrics.readySetSize}`,
|
|
1144
|
+
"",
|
|
1145
|
+
...graphLines,
|
|
1146
|
+
].join("\n");
|
|
1147
|
+
// Build individual subagent prompts for each ready task
|
|
1148
|
+
const subagentSections = [];
|
|
1149
|
+
const readyTaskListLines = [];
|
|
1150
|
+
for (const tid of readyTaskIds) {
|
|
1151
|
+
const node = graph.find((n) => n.id === tid);
|
|
1152
|
+
const tTitle = node?.title ?? tid;
|
|
1153
|
+
readyTaskListLines.push(`- **${tid}: ${tTitle}**`);
|
|
1154
|
+
// Build dependency-scoped carry-forward paths for this task
|
|
1155
|
+
const depPaths = await getDependencyTaskSummaryPaths(mid, sid, tid, node?.dependsOn ?? [], base);
|
|
1156
|
+
// Build a full execute-task prompt with dependency-based carry-forward
|
|
1157
|
+
const taskPrompt = await buildExecuteTaskPrompt(mid, sid, sTitle, tid, tTitle, base, { carryForwardPaths: depPaths });
|
|
1158
|
+
subagentSections.push([
|
|
1159
|
+
`### ${tid}: ${tTitle}`,
|
|
1160
|
+
"",
|
|
1161
|
+
"Use this as the prompt for a `subagent` call:",
|
|
1162
|
+
"",
|
|
1163
|
+
"```",
|
|
1164
|
+
taskPrompt,
|
|
1165
|
+
"```",
|
|
1166
|
+
].join("\n"));
|
|
1167
|
+
}
|
|
1168
|
+
const inlinedTemplates = inlineTemplate("task-summary", "Task Summary");
|
|
1169
|
+
return loadPrompt("reactive-execute", {
|
|
1170
|
+
workingDirectory: base,
|
|
1171
|
+
milestoneId: mid,
|
|
1172
|
+
milestoneTitle: midTitle,
|
|
1173
|
+
sliceId: sid,
|
|
1174
|
+
sliceTitle: sTitle,
|
|
1175
|
+
graphContext,
|
|
1176
|
+
readyTaskCount: String(readyTaskIds.length),
|
|
1177
|
+
readyTaskList: readyTaskListLines.join("\n"),
|
|
1178
|
+
subagentPrompts: subagentSections.join("\n\n---\n\n"),
|
|
1179
|
+
inlinedTemplates,
|
|
1180
|
+
});
|
|
1181
|
+
}
|
|
1093
1182
|
export async function buildRewriteDocsPrompt(mid, midTitle, activeSlice, base, overrides) {
|
|
1094
1183
|
const sid = activeSlice?.id;
|
|
1095
1184
|
const sTitle = activeSlice?.title ?? "";
|
|
@@ -11,7 +11,7 @@ import { clearUnitRuntimeRecord } from "./unit-runtime.js";
|
|
|
11
11
|
import { clearParseCache, parseRoadmap, parsePlan } from "./files.js";
|
|
12
12
|
import { isValidationTerminal } from "./state.js";
|
|
13
13
|
import { nativeConflictFiles, nativeCommit, nativeCheckoutTheirs, nativeAddPaths, nativeMergeAbort, nativeResetHard, } from "./native-git-bridge.js";
|
|
14
|
-
import { resolveMilestonePath, resolveSlicePath, resolveSliceFile, resolveTasksDir, relMilestoneFile, relSliceFile, relSlicePath, relTaskFile, buildMilestoneFileName, buildSliceFileName, buildTaskFileName, resolveMilestoneFile, clearPathCache, resolveGsdRootFile, } from "./paths.js";
|
|
14
|
+
import { resolveMilestonePath, resolveSlicePath, resolveSliceFile, resolveTasksDir, resolveTaskFiles, relMilestoneFile, relSliceFile, relSlicePath, relTaskFile, buildMilestoneFileName, buildSliceFileName, buildTaskFileName, resolveMilestoneFile, clearPathCache, resolveGsdRootFile, } from "./paths.js";
|
|
15
15
|
import { markSliceDoneInRoadmap } from "./roadmap-mutations.js";
|
|
16
16
|
import { existsSync, mkdirSync, readFileSync, writeFileSync, unlinkSync, } from "node:fs";
|
|
17
17
|
import { dirname, join } from "node:path";
|
|
@@ -73,6 +73,9 @@ export function resolveExpectedArtifactPath(unitType, unitId, base) {
|
|
|
73
73
|
}
|
|
74
74
|
case "rewrite-docs":
|
|
75
75
|
return null;
|
|
76
|
+
case "reactive-execute":
|
|
77
|
+
// Reactive execute produces multiple task summaries — verified separately
|
|
78
|
+
return null;
|
|
76
79
|
default:
|
|
77
80
|
return null;
|
|
78
81
|
}
|
|
@@ -105,6 +108,39 @@ export function verifyExpectedArtifact(unitType, unitId, base) {
|
|
|
105
108
|
const content = readFileSync(overridesPath, "utf-8");
|
|
106
109
|
return !content.includes("**Scope:** active");
|
|
107
110
|
}
|
|
111
|
+
// Reactive-execute: verify that each dispatched task's summary exists.
|
|
112
|
+
// The unitId encodes the batch: "{mid}/{sid}/reactive+T02,T03"
|
|
113
|
+
if (unitType === "reactive-execute") {
|
|
114
|
+
const parts = unitId.split("/");
|
|
115
|
+
const mid = parts[0];
|
|
116
|
+
const sidAndBatch = parts[1];
|
|
117
|
+
const batchPart = parts[2]; // "reactive+T02,T03"
|
|
118
|
+
if (!mid || !sidAndBatch || !batchPart)
|
|
119
|
+
return false;
|
|
120
|
+
const sid = sidAndBatch;
|
|
121
|
+
const plusIdx = batchPart.indexOf("+");
|
|
122
|
+
if (plusIdx === -1) {
|
|
123
|
+
// Legacy format "reactive" without batch IDs — fall back to "any summary"
|
|
124
|
+
const tDir = resolveTasksDir(base, mid, sid);
|
|
125
|
+
if (!tDir)
|
|
126
|
+
return false;
|
|
127
|
+
const summaryFiles = resolveTaskFiles(tDir, "SUMMARY");
|
|
128
|
+
return summaryFiles.length > 0;
|
|
129
|
+
}
|
|
130
|
+
const batchIds = batchPart.slice(plusIdx + 1).split(",").filter(Boolean);
|
|
131
|
+
if (batchIds.length === 0)
|
|
132
|
+
return false;
|
|
133
|
+
const tDir = resolveTasksDir(base, mid, sid);
|
|
134
|
+
if (!tDir)
|
|
135
|
+
return false;
|
|
136
|
+
const existingSummaries = new Set(resolveTaskFiles(tDir, "SUMMARY").map((f) => f.replace(/-SUMMARY\.md$/i, "").toUpperCase()));
|
|
137
|
+
// Every dispatched task must have a summary file
|
|
138
|
+
for (const tid of batchIds) {
|
|
139
|
+
if (!existingSummaries.has(tid.toUpperCase()))
|
|
140
|
+
return false;
|
|
141
|
+
}
|
|
142
|
+
return true;
|
|
143
|
+
}
|
|
108
144
|
const absPath = resolveExpectedArtifactPath(unitType, unitId, base);
|
|
109
145
|
// For unit types with no verifiable artifact (null path), the parent directory
|
|
110
146
|
// is missing on disk — treat as stale completion state so the key gets evicted (#313).
|
|
@@ -303,11 +303,16 @@ export async function bootstrapAutoSession(s, ctx, pi, base, verboseMode, reques
|
|
|
303
303
|
// ── Auto-worktree setup ──
|
|
304
304
|
s.originalBasePath = base;
|
|
305
305
|
const isUnderGsdWorktrees = (p) => {
|
|
306
|
+
// Direct layout: /.gsd/worktrees/
|
|
306
307
|
const marker = `${pathSep}.gsd${pathSep}worktrees${pathSep}`;
|
|
307
308
|
if (p.includes(marker))
|
|
308
309
|
return true;
|
|
309
310
|
const worktreesSuffix = `${pathSep}.gsd${pathSep}worktrees`;
|
|
310
|
-
|
|
311
|
+
if (p.endsWith(worktreesSuffix))
|
|
312
|
+
return true;
|
|
313
|
+
// Symlink-resolved layout: /.gsd/projects/<hash>/worktrees/
|
|
314
|
+
const symlinkRe = new RegExp(`\\${pathSep}\\.gsd\\${pathSep}projects\\${pathSep}[a-f0-9]+\\${pathSep}worktrees(?:\\${pathSep}|$)`);
|
|
315
|
+
return symlinkRe.test(p);
|
|
311
316
|
};
|
|
312
317
|
if (s.currentMilestoneId &&
|
|
313
318
|
shouldUseWorktreeIsolation() &&
|
|
@@ -13,6 +13,7 @@ import { existsSync, readFileSync, unlinkSync, readdirSync, } from "node:fs";
|
|
|
13
13
|
import { join, sep as pathSep } from "node:path";
|
|
14
14
|
import { homedir } from "node:os";
|
|
15
15
|
import { safeCopy, safeCopyRecursive } from "./safe-fs.js";
|
|
16
|
+
const gsdHome = process.env.GSD_HOME || join(homedir(), ".gsd");
|
|
16
17
|
// ─── Project Root → Worktree Sync ─────────────────────────────────────────
|
|
17
18
|
/**
|
|
18
19
|
* Sync milestone artifacts from project root INTO worktree before deriveState.
|
|
@@ -75,7 +76,7 @@ export function syncStateToProjectRoot(worktreePath, projectRoot, milestoneId) {
|
|
|
75
76
|
* doesn't falsely trigger staleness (#804).
|
|
76
77
|
*/
|
|
77
78
|
export function readResourceVersion() {
|
|
78
|
-
const agentDir = process.env.GSD_CODING_AGENT_DIR || join(
|
|
79
|
+
const agentDir = process.env.GSD_CODING_AGENT_DIR || join(gsdHome, "agent");
|
|
79
80
|
const manifestPath = join(agentDir, "managed-resources.json");
|
|
80
81
|
try {
|
|
81
82
|
const manifest = JSON.parse(readFileSync(manifestPath, "utf-8"));
|
|
@@ -115,10 +116,17 @@ export function checkResourcesStale(versionOnStart) {
|
|
|
115
116
|
* Returns the corrected base path.
|
|
116
117
|
*/
|
|
117
118
|
export function escapeStaleWorktree(base) {
|
|
118
|
-
|
|
119
|
-
const
|
|
120
|
-
|
|
121
|
-
|
|
119
|
+
// Direct layout: /.gsd/worktrees/
|
|
120
|
+
const directMarker = `${pathSep}.gsd${pathSep}worktrees${pathSep}`;
|
|
121
|
+
let idx = base.indexOf(directMarker);
|
|
122
|
+
if (idx === -1) {
|
|
123
|
+
// Symlink-resolved layout: /.gsd/projects/<hash>/worktrees/
|
|
124
|
+
const symlinkRe = new RegExp(`\\${pathSep}\\.gsd\\${pathSep}projects\\${pathSep}[a-f0-9]+\\${pathSep}worktrees\\${pathSep}`);
|
|
125
|
+
const match = base.match(symlinkRe);
|
|
126
|
+
if (!match || match.index === undefined)
|
|
127
|
+
return base;
|
|
128
|
+
idx = match.index;
|
|
129
|
+
}
|
|
122
130
|
// base is inside .gsd/worktrees/<something> — extract the project root
|
|
123
131
|
const projectRoot = base.slice(0, idx);
|
|
124
132
|
try {
|
|
@@ -374,39 +374,23 @@ export async function stopAuto(ctx, pi, reason) {
|
|
|
374
374
|
unlinkSync(pausedPath);
|
|
375
375
|
}
|
|
376
376
|
catch { /* non-fatal */ }
|
|
377
|
-
|
|
378
|
-
s.
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
377
|
+
// Restore original model before reset() clears the IDs
|
|
378
|
+
if (pi && ctx && s.originalModelId && s.originalModelProvider) {
|
|
379
|
+
const original = ctx.modelRegistry.find(s.originalModelProvider, s.originalModelId);
|
|
380
|
+
if (original)
|
|
381
|
+
await pi.setModel(original);
|
|
382
|
+
}
|
|
383
|
+
// External cleanup (not covered by session reset)
|
|
382
384
|
clearInFlightTools();
|
|
383
|
-
s.lastBudgetAlertLevel = 0;
|
|
384
|
-
s.lastStateRebuildAt = 0;
|
|
385
|
-
s.unitLifetimeDispatches.clear();
|
|
386
|
-
s.currentUnit = null;
|
|
387
|
-
s.autoModeStartModel = null;
|
|
388
|
-
s.currentMilestoneId = null;
|
|
389
|
-
s.originalBasePath = "";
|
|
390
|
-
s.completedUnits = [];
|
|
391
|
-
s.pendingQuickTasks = [];
|
|
392
385
|
clearSliceProgressCache();
|
|
393
386
|
clearActivityLogState();
|
|
394
387
|
resetProactiveHealing();
|
|
395
|
-
|
|
396
|
-
s.pendingVerificationRetry = null;
|
|
397
|
-
s.verificationRetryCount.clear();
|
|
398
|
-
s.pausedSessionFile = null;
|
|
388
|
+
// UI cleanup
|
|
399
389
|
ctx?.ui.setStatus("gsd-auto", undefined);
|
|
400
390
|
ctx?.ui.setWidget("gsd-progress", undefined);
|
|
401
391
|
ctx?.ui.setFooter(undefined);
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
if (original)
|
|
405
|
-
await pi.setModel(original);
|
|
406
|
-
s.originalModelId = null;
|
|
407
|
-
s.originalModelProvider = null;
|
|
408
|
-
}
|
|
409
|
-
s.cmdCtx = null;
|
|
392
|
+
// Reset all session state in one call
|
|
393
|
+
s.reset();
|
|
410
394
|
}
|
|
411
395
|
/**
|
|
412
396
|
* Pause auto-mode without destroying state. Context is preserved.
|
|
@@ -30,8 +30,16 @@ const VALID_CLASSIFICATIONS = [
|
|
|
30
30
|
*/
|
|
31
31
|
export function resolveCapturesPath(basePath) {
|
|
32
32
|
const resolved = resolve(basePath);
|
|
33
|
+
// Direct layout: /.gsd/worktrees/
|
|
33
34
|
const worktreeMarker = `${sep}.gsd${sep}worktrees${sep}`;
|
|
34
|
-
|
|
35
|
+
let idx = resolved.indexOf(worktreeMarker);
|
|
36
|
+
if (idx === -1) {
|
|
37
|
+
// Symlink-resolved layout: /.gsd/projects/<hash>/worktrees/
|
|
38
|
+
const symlinkRe = new RegExp(`\\${sep}\\.gsd\\${sep}projects\\${sep}[a-f0-9]+\\${sep}worktrees\\${sep}`);
|
|
39
|
+
const match = resolved.match(symlinkRe);
|
|
40
|
+
if (match && match.index !== undefined)
|
|
41
|
+
idx = match.index;
|
|
42
|
+
}
|
|
35
43
|
if (idx !== -1) {
|
|
36
44
|
// basePath is inside a worktree — resolve to project root
|
|
37
45
|
const projectRoot = resolved.slice(0, idx);
|
|
@@ -8,12 +8,13 @@
|
|
|
8
8
|
import { existsSync, mkdirSync, readFileSync, readdirSync, renameSync, writeFileSync } from "node:fs";
|
|
9
9
|
import { dirname, join } from "node:path";
|
|
10
10
|
import { homedir } from "node:os";
|
|
11
|
+
const gsdHome = process.env.GSD_HOME || join(homedir(), ".gsd");
|
|
11
12
|
// ─── Registry I/O ───────────────────────────────────────────────────────────
|
|
12
13
|
function getRegistryPath() {
|
|
13
|
-
return join(
|
|
14
|
+
return join(gsdHome, "extensions", "registry.json");
|
|
14
15
|
}
|
|
15
16
|
function getAgentExtensionsDir() {
|
|
16
|
-
return join(
|
|
17
|
+
return join(gsdHome, "agent", "extensions");
|
|
17
18
|
}
|
|
18
19
|
function loadRegistry() {
|
|
19
20
|
const filePath = getRegistryPath();
|
|
@@ -10,7 +10,7 @@ import { deriveState } from "./state.js";
|
|
|
10
10
|
import { gsdRoot } from "./paths.js";
|
|
11
11
|
import { appendCapture, hasPendingCaptures, loadPendingCaptures } from "./captures.js";
|
|
12
12
|
import { appendOverride, appendKnowledge } from "./files.js";
|
|
13
|
-
import { formatDoctorIssuesForPrompt, formatDoctorReport, runGSDDoctor, selectDoctorScope, filterDoctorIssues, } from "./doctor.js";
|
|
13
|
+
import { formatDoctorIssuesForPrompt, formatDoctorReport, formatDoctorReportJson, runGSDDoctor, selectDoctorScope, filterDoctorIssues, } from "./doctor.js";
|
|
14
14
|
import { isAutoActive } from "./auto.js";
|
|
15
15
|
import { projectRoot } from "./commands.js";
|
|
16
16
|
import { loadPrompt } from "./prompt-loader.js";
|
|
@@ -28,15 +28,28 @@ export function dispatchDoctorHeal(pi, scope, reportText, structuredIssues) {
|
|
|
28
28
|
}
|
|
29
29
|
export async function handleDoctor(args, ctx, pi) {
|
|
30
30
|
const trimmed = args.trim();
|
|
31
|
-
|
|
31
|
+
// Extract flags before positional parsing
|
|
32
|
+
const jsonMode = trimmed.includes("--json");
|
|
33
|
+
const dryRun = trimmed.includes("--dry-run");
|
|
34
|
+
const includeBuild = trimmed.includes("--build");
|
|
35
|
+
const includeTests = trimmed.includes("--test");
|
|
36
|
+
const stripped = trimmed.replace(/--json|--dry-run|--build|--test/g, "").trim();
|
|
37
|
+
const parts = stripped ? stripped.split(/\s+/) : [];
|
|
32
38
|
const mode = parts[0] === "fix" || parts[0] === "heal" || parts[0] === "audit" ? parts[0] : "doctor";
|
|
33
39
|
const requestedScope = mode === "doctor" ? parts[0] : parts[1];
|
|
34
40
|
const scope = await selectDoctorScope(projectRoot(), requestedScope);
|
|
35
41
|
const effectiveScope = mode === "audit" ? requestedScope : scope;
|
|
36
42
|
const report = await runGSDDoctor(projectRoot(), {
|
|
37
|
-
fix: mode === "fix" || mode === "heal",
|
|
43
|
+
fix: mode === "fix" || mode === "heal" || dryRun,
|
|
44
|
+
dryRun,
|
|
38
45
|
scope: effectiveScope,
|
|
46
|
+
includeBuild,
|
|
47
|
+
includeTests,
|
|
39
48
|
});
|
|
49
|
+
if (jsonMode) {
|
|
50
|
+
ctx.ui.notify(formatDoctorReportJson(report), "info");
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
40
53
|
const reportText = formatDoctorReport(report, {
|
|
41
54
|
scope: effectiveScope,
|
|
42
55
|
includeWarnings: mode === "audit",
|