create-agdf 0.9.1 → 0.9.5

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 (37) hide show
  1. package/README.md +33 -4
  2. package/bin/create-agdf.js +2 -3333
  3. package/generated/.agdf/control/templates/MASTER_BACKLOG.md +3 -3
  4. package/generated/.github/skills/agdf-gate-check/SKILL.md +5 -0
  5. package/generated/.github/skills/agdf-interaction-locales.json +36 -0
  6. package/generated/.github/skills/contracts/interaction.md +9 -1
  7. package/generated/.opencode/agdf-interaction-locales.json +36 -0
  8. package/generated/.opencode/contracts/interaction.md +9 -1
  9. package/generated/.opencode/skills/agdf-gate-check/SKILL.md +5 -0
  10. package/generated/plugins/agdf/.codex-plugin/plugin.json +1 -1
  11. package/generated/plugins/agdf/control/templates/MASTER_BACKLOG.md +3 -3
  12. package/generated/plugins/agdf/meta/agdf-interaction-locales.json +36 -0
  13. package/generated/plugins/agdf/meta/agdf-plugin.definition.json +1 -1
  14. package/generated/plugins/agdf/meta/contracts/interaction.md +9 -1
  15. package/generated/plugins/agdf/skills/gate-check/SKILL.md +5 -0
  16. package/lib/cli/application.js +342 -0
  17. package/lib/cli/command-registry.js +119 -0
  18. package/lib/cli/delivery-path-search-command.js +75 -0
  19. package/lib/cli/parse-args.js +150 -0
  20. package/lib/cli/runtime-context.js +83 -0
  21. package/lib/control-evaluation/delivery-map.js +291 -0
  22. package/lib/control-evaluation/doctor.js +265 -0
  23. package/lib/control-evaluation/gate-check.js +404 -0
  24. package/lib/control-evaluation/gate-policy.js +283 -0
  25. package/lib/control-evaluation/run-state.js +217 -0
  26. package/lib/control-evaluation/shared.js +344 -0
  27. package/lib/control-evaluation/verified-change.js +156 -0
  28. package/lib/installers/opencode.js +443 -0
  29. package/lib/installers/plugin-installers.js +99 -0
  30. package/lib/lifecycle/operations.js +177 -0
  31. package/lib/lifecycle/presentation.js +52 -0
  32. package/lib/lifecycle/result.js +69 -0
  33. package/lib/lifecycle/status.js +108 -0
  34. package/lib/scaffold/plan.js +278 -0
  35. package/lib/scaffold/presentation.js +98 -0
  36. package/lib/scaffold/write.js +45 -0
  37. package/package.json +4 -2
@@ -0,0 +1,177 @@
1
+ import { execFileSync } from "node:child_process";
2
+ import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
3
+ import { dirname, join } from "node:path";
4
+ import { pluginDefinition } from "../cli/runtime-context.js";
5
+ import { evaluateOpenCodeStatus, openCodeNpmInvocation } from "../installers/opencode.js";
6
+ import { inspectPluginSurface } from "../installers/plugin-installers.js";
7
+
8
+ const CODEX_DISABLE_MARKER = "# AGDF-OWNED-REPOSITORY-PLUGIN-STATE";
9
+
10
+ function pluginSection(content, selector) {
11
+ const escaped = JSON.stringify(selector).replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
12
+ return content.match(new RegExp(`\\[plugins\\.${escaped}\\][\\s\\S]*?(?=\\n\\[|$)`))?.[0] ?? "";
13
+ }
14
+
15
+ function repositorySelector(targetDir) {
16
+ return existsSync(join(targetDir, ".agents", "plugins", "marketplace.json")) ? "agdf@agdf-repo" : "agdf@agdf";
17
+ }
18
+
19
+ export function planRepositoryDisable(targetDir, surface) {
20
+ if (surface !== "codex") {
21
+ throw new Error(`Repository disable is not supported safely for ${surface}; no files were changed.`);
22
+ }
23
+ const path = join(targetDir, ".codex", "config.toml");
24
+ const existing = existsSync(path) ? readFileSync(path, "utf8") : "";
25
+ const selector = repositorySelector(targetDir);
26
+ const escaped = JSON.stringify(selector).replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
27
+ const sectionPattern = new RegExp(`\\[plugins\\.${escaped}\\][\\s\\S]*?(?=\\n\\[|$)`);
28
+ const currentSection = pluginSection(existing, selector);
29
+ const enabledMatches = [...currentSection.matchAll(/^enabled\s*=\s*(true|false)\s*$/gm)];
30
+ if (currentSection && enabledMatches.length !== 1) {
31
+ throw new Error(`Refusing to modify ambiguous AGDF plugin state in ${path}.`);
32
+ }
33
+ const section = currentSection
34
+ ? currentSection.replace(/^enabled\s*=\s*(true|false)\s*$/m, "enabled = false")
35
+ : `${CODEX_DISABLE_MARKER}\n[plugins.${JSON.stringify(selector)}]\nenabled = false`;
36
+ const content = currentSection
37
+ ? existing.replace(sectionPattern, section).replace(/\s*$/, "\n")
38
+ : `${existing.replace(/\s*$/, "")}${existing.trim() ? "\n\n" : ""}${section}\n`;
39
+ return Object.freeze({
40
+ operation: "disable",
41
+ surface,
42
+ scope: "repository",
43
+ mutations: Object.freeze([{ kind: "write", path, content, ownership: currentSection ? "exact_plugin_section" : "agdf_marker" }]),
44
+ retained: Object.freeze([join(targetDir, ".agdf", "control"), "global AGDF plugin availability"]),
45
+ expected: Object.freeze({ repository_status: "disabled" }),
46
+ });
47
+ }
48
+
49
+ export function verifyRepositoryDisabled(targetDir) {
50
+ const path = join(targetDir, ".codex", "config.toml");
51
+ if (!existsSync(path)) return { status: "failed", evidence: [`missing:${path}`] };
52
+ const selector = repositorySelector(targetDir);
53
+ const section = pluginSection(readFileSync(path, "utf8"), selector);
54
+ const matches = [...section.matchAll(/^enabled\s*=\s*(true|false)\s*$/gm)];
55
+ return matches.length === 1 && matches[0][1] === "false"
56
+ ? { status: "healthy", evidence: [`${path}:${selector}:enabled=false`] }
57
+ : { status: "failed", evidence: [`postcondition_failed:${path}:${selector}`] };
58
+ }
59
+
60
+ export function verifyGlobalUninstall(plan, targetDir, { configDir, exec, inspect = inspectPluginSurface, evaluateOpenCode = evaluateOpenCodeStatus } = {}) {
61
+ if (plan.surface === "opencode") {
62
+ const report = evaluateOpenCode(targetDir, configDir);
63
+ const remainingOwnedFiles = plan.mutations
64
+ .filter((mutation) => mutation.kind === "remove")
65
+ .map((mutation) => mutation.path)
66
+ .filter((path) => existsSync(path));
67
+ return report.status === "not_configured" && remainingOwnedFiles.length === 0
68
+ ? { status: "healthy", evidence: [report.global_config.path, "marker_owned_global_files_removed"] }
69
+ : { status: "failed", evidence: [report.global_config.path, ...remainingOwnedFiles.map((path) => `still_present:${path}`)] };
70
+ }
71
+ const report = inspect(plan.surface, exec);
72
+ return report.status === "not_installed"
73
+ ? { status: "healthy", evidence: report.evidence }
74
+ : { status: "failed", evidence: [...report.evidence, `observed:${report.status}`] };
75
+ }
76
+
77
+ export function planGlobalUninstall(surface, { configDir } = {}) {
78
+ if (surface === "codex") {
79
+ return nativeUninstallPlan(surface, "codex", ["plugin", "remove", "agdf@agdf"]);
80
+ }
81
+ if (surface === "claude") {
82
+ return nativeUninstallPlan(surface, "claude", ["plugin", "uninstall", "agdf@agdf", "--scope", "user"]);
83
+ }
84
+ if (surface === "opencode") {
85
+ if (!configDir) throw new Error("OpenCode uninstall requires its explicit config directory.");
86
+ const configPath = join(configDir, "opencode.json");
87
+ if (!existsSync(configPath)) throw new Error(`OpenCode config not found: ${configPath}`);
88
+ let config;
89
+ try {
90
+ config = JSON.parse(readFileSync(configPath, "utf8"));
91
+ } catch {
92
+ throw new Error(`Refusing to modify unreadable OpenCode config: ${configPath}`);
93
+ }
94
+ if (config.plugin !== undefined && !Array.isArray(config.plugin)) {
95
+ throw new Error(`Refusing to modify OpenCode config with non-array plugin field: ${configPath}`);
96
+ }
97
+ const next = { ...config };
98
+ next.plugin = (config.plugin ?? []).filter((entry) => entry !== pluginDefinition.opencode.npmPackage);
99
+ if (Array.isArray(config.instructions)) next.instructions = config.instructions.filter((entry) => entry !== "AGDF.md");
100
+ const npm = openCodeNpmInvocation(["uninstall", "--silent", pluginDefinition.opencode.npmPackage]);
101
+ const mutations = [
102
+ { kind: "write", path: configPath, content: `${JSON.stringify(next, null, 2)}\n`, ownership: "exact_known_entries" },
103
+ { kind: "command", executable: npm.executable, args: npm.args, cwd: configDir },
104
+ ];
105
+ const retained = ["repository AGDF files", ".agdf/control"];
106
+ for (const candidate of openCodeOwnedGlobalFiles(configDir)) {
107
+ if (!existsSync(candidate.path)) continue;
108
+ const content = readFileSync(candidate.path, "utf8");
109
+ if (candidate.owned(content)) mutations.push({ kind: "remove", path: candidate.path, ownership: "agdf_marker" });
110
+ else retained.push(candidate.path);
111
+ }
112
+ return Object.freeze({
113
+ operation: "uninstall",
114
+ surface,
115
+ scope: "global",
116
+ mutations: Object.freeze(mutations),
117
+ retained: Object.freeze(retained),
118
+ expected: Object.freeze({ installation_status: "not_installed" }),
119
+ });
120
+ }
121
+ throw new Error(`Global uninstall is not supported for ${surface}.`);
122
+ }
123
+
124
+ function openCodeOwnedGlobalFiles(configDir) {
125
+ const firstLine = (marker) => (content) => content.split(/\r?\n/)[0] === marker;
126
+ const afterFrontmatter = (marker) => (content) => {
127
+ const lines = content.split(/\r?\n/);
128
+ const end = lines.findIndex((line, index) => index > 0 && line === "---");
129
+ return end >= 0 && lines[end + 1] === marker;
130
+ };
131
+ const files = [
132
+ { path: join(configDir, pluginDefinition.opencode.instructionsFileName), owned: firstLine("<!-- AGDF-GLOBAL-INSTRUCTIONS -->") },
133
+ { path: join(configDir, pluginDefinition.opencode.runtimeContractFileName), owned: firstLine("<!-- AGDF-GLOBAL-RUNTIME-CONTRACT -->") },
134
+ ];
135
+ for (const moduleName of ["gate-transition.md", "interaction.md", "modes.md", "quality.md", "context-graph.md", "control-scaffold.md", "closeout.md"]) {
136
+ files.push({ path: join(configDir, "contracts", moduleName), owned: firstLine("<!-- AGDF-GLOBAL-RUNTIME-CONTRACT -->") });
137
+ }
138
+ for (const skill of pluginDefinition.skillSet) {
139
+ const name = `${pluginDefinition.opencode.globalSkillPrefix}${skill.slug}`;
140
+ const marker = `<!-- AGDF-GLOBAL-SKILL: ${name} -->`;
141
+ files.push({ path: join(configDir, "skills", name, "SKILL.md"), owned: afterFrontmatter(marker) });
142
+ }
143
+ return files;
144
+ }
145
+
146
+ function nativeUninstallPlan(surface, executable, args) {
147
+ return Object.freeze({
148
+ operation: "uninstall",
149
+ surface,
150
+ scope: "global",
151
+ mutations: Object.freeze([{ kind: "command", executable, args: Object.freeze(args) }]),
152
+ retained: Object.freeze(["repository AGDF files", ".agdf/control", "ambiguous configuration"]),
153
+ expected: Object.freeze({ installation_status: "not_installed" }),
154
+ });
155
+ }
156
+
157
+ export function applyLifecyclePlan(plan, { exec = execFileSync } = {}) {
158
+ const completed = [];
159
+ for (const mutation of plan.mutations) {
160
+ try {
161
+ if (mutation.kind === "write") {
162
+ mkdirSync(dirname(mutation.path), { recursive: true });
163
+ writeFileSync(mutation.path, mutation.content, "utf8");
164
+ completed.push({ kind: "write", path: mutation.path });
165
+ } else if (mutation.kind === "remove") {
166
+ rmSync(mutation.path);
167
+ completed.push({ kind: "remove", path: mutation.path });
168
+ } else if (mutation.kind === "command") {
169
+ exec(mutation.executable, mutation.args, { cwd: mutation.cwd, stdio: "inherit" });
170
+ completed.push({ kind: "command", executable: mutation.executable, args: mutation.args });
171
+ }
172
+ } catch (error) {
173
+ return { status: completed.length ? "partial" : "failed", completed, error, retained: [...plan.retained] };
174
+ }
175
+ }
176
+ return { status: "success", completed, error: null, retained: [...plan.retained] };
177
+ }
@@ -0,0 +1,52 @@
1
+ import { interactionLocales } from "../cli/runtime-context.js";
2
+
3
+ function copy(locale) {
4
+ const language = String(locale ?? "").toLowerCase().split("-")[0];
5
+ return (interactionLocales.locales[language] ?? interactionLocales.locales[interactionLocales.fallbackLocale]).primary.lifecycleResult;
6
+ }
7
+
8
+ export function lifecycleCardLines(report, locale = "en") {
9
+ const c = copy(locale);
10
+ const version = report.version.transition === "updated" && report.version.previous && report.version.installed
11
+ ? `${report.version.previous} -> ${report.version.installed}`
12
+ : report.version.installed || (report.version.expected ? `${c.unknown}; expected ${report.version.expected}` : c.unknown);
13
+ const versionStatus = report.version.transition && report.version.transition !== "updated"
14
+ ? `${report.version.status}; transition ${report.version.transition}`
15
+ : report.version.status;
16
+ return [
17
+ c.title,
18
+ `${c.result}: ${report.result}`,
19
+ `${c.surface}: ${report.surface}`,
20
+ `${c.scope}: ${report.scope}`,
21
+ `${c.version}: ${version} (${versionStatus})`,
22
+ `${c.verification}: ${report.verification.status}`,
23
+ `${c.restart}: ${report.restart.required ? c.yes : c.no}${report.restart.reason !== "none" ? ` (${report.restart.reason})` : ""}`,
24
+ `${c.next}: ${report.next_action.text}`,
25
+ ];
26
+ }
27
+
28
+ export function printLifecycleResult(report, { json = false, locale = "en", io = console } = {}) {
29
+ if (json) {
30
+ io.log(JSON.stringify(report, null, 2));
31
+ return;
32
+ }
33
+ for (const line of lifecycleCardLines(report, locale)) io.log(line);
34
+ if (report.failure) io.log(`Failure phase: ${report.failure.phase}: ${report.failure.message}`);
35
+ if (report.retained.length) {
36
+ io.log("Retained:");
37
+ for (const item of report.retained) io.log(`- ${typeof item === "string" ? item : JSON.stringify(item)}`);
38
+ }
39
+ }
40
+
41
+ export function printGeneralStatus(report, { json = false, locale = "en", io = console } = {}) {
42
+ if (json) {
43
+ io.log(JSON.stringify(report, null, 2));
44
+ return;
45
+ }
46
+ const c = copy(locale);
47
+ io.log(c.statusTitle);
48
+ io.log(`${c.installation}: ${report.installation.status}${report.installation.version ? ` (${report.installation.version})` : ""}`);
49
+ io.log(`${c.repository}: ${report.repository.status}`);
50
+ io.log(`${c.delivery}: ${report.delivery.status}${report.delivery.current_gate ? ` (${report.delivery.current_gate})` : ""}`);
51
+ io.log(`${c.next}: ${report.next_action.text}`);
52
+ }
@@ -0,0 +1,69 @@
1
+ const OPERATIONS = new Set(["install", "update", "status", "disable", "uninstall", "repository_setup"]);
2
+ const RESULTS = new Set(["success", "partial", "failed", "preview"]);
3
+ const SCOPES = new Set(["global", "repository"]);
4
+
5
+ function text(value) {
6
+ return typeof value === "string" ? value.trim() : "";
7
+ }
8
+
9
+ export function createLifecycleResult(input = {}) {
10
+ const result = {
11
+ schema_version: 1,
12
+ operation: text(input.operation),
13
+ result: text(input.result),
14
+ surface: text(input.surface),
15
+ scope: text(input.scope),
16
+ version: {
17
+ expected: text(input.version?.expected) || null,
18
+ installed: text(input.version?.installed) || null,
19
+ previous: text(input.version?.previous) || null,
20
+ status: text(input.version?.status) || "unknown",
21
+ transition: text(input.version?.transition) || null,
22
+ },
23
+ verification: {
24
+ status: text(input.verification?.status) || "unknown",
25
+ evidence: [...(input.verification?.evidence ?? [])].map(String),
26
+ },
27
+ restart: {
28
+ required: Boolean(input.restart?.required),
29
+ reason: text(input.restart?.reason) || "none",
30
+ },
31
+ next_action: {
32
+ kind: text(input.next_action?.kind) || "none",
33
+ text: text(input.next_action?.text) || "No further action required.",
34
+ },
35
+ changes: [...(input.changes ?? [])],
36
+ retained: [...(input.retained ?? [])],
37
+ failure: input.failure ?? null,
38
+ };
39
+ assertLifecycleResult(result);
40
+ return Object.freeze(result);
41
+ }
42
+
43
+ export function assertLifecycleResult(value) {
44
+ if (value?.schema_version !== 1) throw new Error("Lifecycle result requires schema_version 1.");
45
+ if (!OPERATIONS.has(value.operation)) throw new Error(`Unsupported lifecycle operation: ${value.operation || "missing"}.`);
46
+ if (!RESULTS.has(value.result)) throw new Error(`Unsupported lifecycle result: ${value.result || "missing"}.`);
47
+ if (!value.surface) throw new Error("Lifecycle result requires a surface.");
48
+ if (!SCOPES.has(value.scope)) throw new Error(`Unsupported lifecycle scope: ${value.scope || "missing"}.`);
49
+ if (!value.next_action || Array.isArray(value.next_action) || !text(value.next_action.text)) {
50
+ throw new Error("Lifecycle result requires exactly one next action.");
51
+ }
52
+ if (value.version.status === "verified" && !value.version.installed) {
53
+ throw new Error("A verified lifecycle version requires an observed installed version.");
54
+ }
55
+ if (value.result === "failed" && !value.failure) throw new Error("Failed lifecycle results require failure evidence.");
56
+ return value;
57
+ }
58
+
59
+ export function lifecycleFailure({ operation, surface, scope, phase, message, evidence = [], nextAction }) {
60
+ return createLifecycleResult({
61
+ operation,
62
+ result: "failed",
63
+ surface,
64
+ scope,
65
+ verification: { status: "degraded", evidence },
66
+ next_action: { kind: "recovery", text: nextAction },
67
+ failure: { phase, message },
68
+ });
69
+ }
@@ -0,0 +1,108 @@
1
+ import { existsSync, readFileSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { pluginDefinition } from "../cli/runtime-context.js";
4
+ import { evaluateDoctor } from "../control-evaluation/doctor.js";
5
+ import { evaluateGateCheck } from "../control-evaluation/gate-check.js";
6
+ import { evaluateOpenCodeStatus } from "../installers/opencode.js";
7
+ import { inspectPluginSurface } from "../installers/plugin-installers.js";
8
+
9
+ function deliveryStatus(targetDir, selection, dependencies) {
10
+ const controlDir = join(targetDir, ".agdf", "control");
11
+ const hasLiveControl = existsSync(join(controlDir, "runs"))
12
+ || (existsSync(join(controlDir, "AGDF_RUN.md")) && existsSync(join(controlDir, "MASTER_BACKLOG.md")));
13
+ if (!hasLiveControl) {
14
+ return { status: "not_configured", run_id: null, current_gate: null, evidence: [] };
15
+ }
16
+ try {
17
+ const doctor = dependencies.evaluateDoctor(targetDir, selection);
18
+ const gate = dependencies.evaluateGateCheck(targetDir, selection);
19
+ return {
20
+ status: gate.status === "blocked" || doctor.status === "block" ? "blocked" : gate.status === "complete" ? "complete" : "open",
21
+ run_id: selection.runId || null,
22
+ current_gate: gate.current_gate || null,
23
+ evidence: [`doctor:${doctor.status}`, `gate-check:${gate.status}`],
24
+ };
25
+ } catch (error) {
26
+ return { status: "unknown", run_id: selection.runId || null, current_gate: null, evidence: [error.message] };
27
+ }
28
+ }
29
+
30
+ function repositoryStatus(targetDir, surface) {
31
+ if (surface === "codex") {
32
+ const marketplace = join(targetDir, ".agents", "plugins", "marketplace.json");
33
+ const disabled = join(targetDir, ".codex", "config.toml");
34
+ const config = existsSync(disabled) ? readFileSync(disabled, "utf8") : "";
35
+ const isDisabled = ["agdf@agdf-repo", "agdf@agdf"].some((selector) => {
36
+ const escaped = JSON.stringify(selector).replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
37
+ const section = config.match(new RegExp(`\\[plugins\\.${escaped}\\][\\s\\S]*?(?=\\n\\[|$)`))?.[0] ?? "";
38
+ return /^enabled\s*=\s*false\s*$/m.test(section);
39
+ });
40
+ return { status: isDisabled ? "disabled" : existsSync(marketplace) ? "active" : "not_configured", scope: "repository", evidence: [marketplace] };
41
+ }
42
+ if (surface === "opencode") {
43
+ const instructions = join(targetDir, ".opencode", pluginDefinition.opencode.instructionsFileName);
44
+ return { status: existsSync(instructions) ? "active" : "not_configured", scope: "repository", evidence: [instructions] };
45
+ }
46
+ return { status: "unknown", scope: "repository", evidence: [] };
47
+ }
48
+
49
+ function inspectInstallation(surface, targetDir, options, deps, exec) {
50
+ if (surface === "opencode") {
51
+ const report = deps.evaluateOpenCodeStatus(targetDir, options.configDir);
52
+ return {
53
+ status: report.status === "configured" && report.package.version_status === "current" ? "healthy" : report.status === "configured" ? "degraded" : "not_installed",
54
+ surface,
55
+ version: report.package.installed_version,
56
+ evidence: [report.global_config.path],
57
+ };
58
+ }
59
+ if (["codex", "claude"].includes(surface)) return deps.inspectPluginSurface(surface, exec);
60
+ return { status: "unknown", surface, version: null, evidence: [`unsupported_status_probe:${surface}`] };
61
+ }
62
+
63
+ export function evaluateGeneralStatus(targetDir, options = {}, dependencies = {}) {
64
+ const deps = {
65
+ evaluateDoctor: dependencies.evaluateDoctor ?? evaluateDoctor,
66
+ evaluateGateCheck: dependencies.evaluateGateCheck ?? evaluateGateCheck,
67
+ evaluateOpenCodeStatus: dependencies.evaluateOpenCodeStatus ?? evaluateOpenCodeStatus,
68
+ inspectPluginSurface: dependencies.inspectPluginSurface ?? inspectPluginSurface,
69
+ };
70
+ let surface = options.surface && options.surface !== "generic" ? options.surface : null;
71
+ let installation;
72
+ if (surface) {
73
+ installation = inspectInstallation(surface, targetDir, options, deps, dependencies.exec);
74
+ } else {
75
+ const probes = ["codex", "claude", "opencode"].map((candidate) => inspectInstallation(candidate, targetDir, options, deps, dependencies.exec));
76
+ const observed = probes.filter((probe) => ["healthy", "degraded"].includes(probe.status));
77
+ if (observed.length === 1) {
78
+ installation = observed[0];
79
+ surface = installation.surface;
80
+ } else {
81
+ surface = observed.length > 1 ? "multiple" : "generic";
82
+ installation = {
83
+ status: "unknown",
84
+ surface,
85
+ version: null,
86
+ evidence: probes.flatMap((probe) => probe.evidence.map((item) => `${probe.surface}:${item}`)),
87
+ };
88
+ }
89
+ }
90
+ const repository = repositoryStatus(targetDir, surface);
91
+ const delivery = deliveryStatus(targetDir, { runId: options.runId }, deps);
92
+ const nextText = surface === "multiple"
93
+ ? "Multiple AGDF host surfaces were detected; rerun status with an explicit --surface."
94
+ : surface === "generic"
95
+ ? "No installed AGDF host surface could be selected safely; rerun status with an explicit --surface."
96
+ : installation.status !== "healthy"
97
+ ? `Run npx --yes @agdf/cli@latest ${surface} to install or repair AGDF.`
98
+ : repository.status === "disabled"
99
+ ? "Restart the host to apply the repository-local disable; the healthy global AGDF installation remains available."
100
+ : repository.status === "not_configured"
101
+ ? `Run npx --yes @agdf/cli@latest ${surface === "codex" ? "codex-repo" : `${surface}-repo`} in this repository.`
102
+ : delivery.status === "blocked"
103
+ ? "Resolve the reported delivery blocker without changing the healthy installation."
104
+ : delivery.status === "not_configured"
105
+ ? "Start a new task; status created no run and requires no approval. Initialize durable control only when the repository needs it."
106
+ : "Continue with the reported delivery next step.";
107
+ return { schema_version: 1, installation, repository, delivery, next_action: { kind: "action", text: nextText } };
108
+ }