@tacuchi/agent-workflow-cli 25.1.2 → 25.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (92) hide show
  1. package/README.md +2 -1
  2. package/dist/adapters/node-process.js +35 -0
  3. package/dist/adapters/node-process.js.map +1 -1
  4. package/dist/application/amend-service.js +393 -0
  5. package/dist/application/amend-service.js.map +1 -0
  6. package/dist/application/doctor/actions.js +158 -0
  7. package/dist/application/doctor/actions.js.map +1 -0
  8. package/dist/application/doctor/apply.js +399 -0
  9. package/dist/application/doctor/apply.js.map +1 -0
  10. package/dist/application/doctor/auth-dsn.js +143 -0
  11. package/dist/application/doctor/auth-dsn.js.map +1 -0
  12. package/dist/application/doctor/auth-flow.js +28 -0
  13. package/dist/application/doctor/auth-flow.js.map +1 -0
  14. package/dist/application/doctor/auth-registry.js +12 -0
  15. package/dist/application/doctor/auth-registry.js.map +1 -0
  16. package/dist/application/doctor/hosts.js +41 -0
  17. package/dist/application/doctor/hosts.js.map +1 -0
  18. package/dist/application/doctor/native-host-state.js +211 -0
  19. package/dist/application/doctor/native-host-state.js.map +1 -0
  20. package/dist/application/doctor/prepare.js +262 -0
  21. package/dist/application/doctor/prepare.js.map +1 -0
  22. package/dist/application/doctor/provider-installation.js +124 -0
  23. package/dist/application/doctor/provider-installation.js.map +1 -0
  24. package/dist/application/doctor/provider-mcps.js +650 -0
  25. package/dist/application/doctor/provider-mcps.js.map +1 -0
  26. package/dist/application/doctor/provider-plugins-hooks.js +112 -0
  27. package/dist/application/doctor/provider-plugins-hooks.js.map +1 -0
  28. package/dist/application/doctor/provider-skills.js +212 -0
  29. package/dist/application/doctor/provider-skills.js.map +1 -0
  30. package/dist/application/doctor/provider-tools-auth.js +201 -0
  31. package/dist/application/doctor/provider-tools-auth.js.map +1 -0
  32. package/dist/application/doctor/provider-visibility.js +149 -0
  33. package/dist/application/doctor/provider-visibility.js.map +1 -0
  34. package/dist/application/doctor/repair-runner.js +169 -0
  35. package/dist/application/doctor/repair-runner.js.map +1 -0
  36. package/dist/application/doctor/report.js +155 -0
  37. package/dist/application/doctor/report.js.map +1 -0
  38. package/dist/application/doctor/types.js +5 -0
  39. package/dist/application/doctor/types.js.map +1 -0
  40. package/dist/application/flow/advance.js +62 -9
  41. package/dist/application/flow/advance.js.map +1 -1
  42. package/dist/application/flow/flow-service.js +18 -7
  43. package/dist/application/flow/flow-service.js.map +1 -1
  44. package/dist/application/flow/run-projection.js +6 -1
  45. package/dist/application/flow/run-projection.js.map +1 -1
  46. package/dist/application/flow/submit.js +143 -3
  47. package/dist/application/flow/submit.js.map +1 -1
  48. package/dist/application/mcp-doctor-service.js +1 -1
  49. package/dist/application/mcp-doctor-service.js.map +1 -1
  50. package/dist/application/mcp-host-reader.js +134 -26
  51. package/dist/application/mcp-host-reader.js.map +1 -1
  52. package/dist/application/mcp-host-receipt-service.js +9 -7
  53. package/dist/application/mcp-host-receipt-service.js.map +1 -1
  54. package/dist/application/self/install-skill.js +10 -2
  55. package/dist/application/self/install-skill.js.map +1 -1
  56. package/dist/application/self/mcp-config.js +1 -1
  57. package/dist/application/self/mcp-config.js.map +1 -1
  58. package/dist/application/session-narrative.js +15 -0
  59. package/dist/application/session-narrative.js.map +1 -1
  60. package/dist/application/source-boundary-policy.js +87 -6
  61. package/dist/application/source-boundary-policy.js.map +1 -1
  62. package/dist/cli/commands/amend.js +122 -0
  63. package/dist/cli/commands/amend.js.map +1 -0
  64. package/dist/cli/commands/doctor.js +300 -0
  65. package/dist/cli/commands/doctor.js.map +1 -0
  66. package/dist/cli/commands/index.js +4 -0
  67. package/dist/cli/commands/index.js.map +1 -1
  68. package/dist/cli/dispatch-plan.js +47 -0
  69. package/dist/cli/dispatch-plan.js.map +1 -0
  70. package/dist/cli/help-groups.js +8 -0
  71. package/dist/cli/help-groups.js.map +1 -1
  72. package/dist/cli/main.js +54 -10
  73. package/dist/cli/main.js.map +1 -1
  74. package/dist/cli/parser.js +20 -0
  75. package/dist/cli/parser.js.map +1 -1
  76. package/dist/domain/doctor/auth.js +65 -0
  77. package/dist/domain/doctor/auth.js.map +1 -0
  78. package/dist/domain/doctor/model.js +82 -0
  79. package/dist/domain/doctor/model.js.map +1 -0
  80. package/dist/domain/doctor/operations.js +114 -0
  81. package/dist/domain/doctor/operations.js.map +1 -0
  82. package/dist/domain/flow/authority.js +8 -0
  83. package/dist/domain/flow/authority.js.map +1 -1
  84. package/dist/domain/flow/run-state.js +181 -25
  85. package/dist/domain/flow/run-state.js.map +1 -1
  86. package/dist/domain/redaction.js +40 -0
  87. package/dist/domain/redaction.js.map +1 -1
  88. package/package.json +1 -1
  89. package/skills/w/SKILL.md +2 -1
  90. package/skills/w/commands/README.md +2 -1
  91. package/skills/w/commands/doctor.md +31 -0
  92. package/skills/w/context/MANIFEST.json +4 -0
@@ -0,0 +1,149 @@
1
+ import { doctorFindingId } from "../../domain/doctor/model.js";
2
+ /**
3
+ * Workspace visibility — the declared sources against the ones each host has
4
+ * registered.
5
+ *
6
+ * One finding per drift CLASS that is actually present, driven by the `missing`
7
+ * and `extra` lists rather than by the aggregate status: a report whose status
8
+ * is `missing-paths` may also carry extras, and a single finding per host would
9
+ * hide one of the two.
10
+ *
11
+ * With no project block in the current directory there is nothing to compare,
12
+ * and that is `not-applicable` — not a warning. Someone running the doctor from
13
+ * a directory that is not a workspace has not misconfigured anything.
14
+ */
15
+ import { harnessForMcpHost } from "../../domain/harnesses.js";
16
+ import { runVisibilityDoctor } from "../visibility-doctor-service.js";
17
+ import { coverage } from "./types.js";
18
+ const CATEGORY = "workspace-visibility";
19
+ /**
20
+ * The visibility engine speaks `McpHost` ids and the report speaks catalog ids.
21
+ *
22
+ * They differ for exactly one host — `claude` against `claude-code` — and that
23
+ * one difference is enough to make a report list the same machine twice under
24
+ * two names. Translated at the boundary, once.
25
+ */
26
+ function catalogHost(host) {
27
+ return harnessForMcpHost(host)?.id ?? host;
28
+ }
29
+ export const visibilityProvider = {
30
+ category: CATEGORY,
31
+ async run(input) {
32
+ const result = await runVisibilityDoctor(input.ctx.fs, input.ctx.env, input.ctx.paths, {
33
+ workspace: input.workspaceDir,
34
+ global: true,
35
+ });
36
+ const reports = [...result.reports, ...result.global_reports];
37
+ const findings = [];
38
+ const coverages = [];
39
+ // The visibility engine always inspects its own hardcoded set of hosts; the
40
+ // run's selection is the aggregator's answer, and it is the one that counts.
41
+ // `selectDoctorHosts` already split the participants from the hosts with no
42
+ // trace here and applied `--only`. Without this filter the report enumerated
43
+ // a host as absent and, two sections below, declared it `comprobada ✔` with
44
+ // a healthy finding — and `--only kimi` still diagnosed claude-code, codex
45
+ // and warp.
46
+ const participants = new Set(input.hosts.map((host) => host.host));
47
+ for (const report of reports) {
48
+ const host = catalogHost(report.host);
49
+ if (!participants.has(host))
50
+ continue;
51
+ if (report.status === "no-project-block") {
52
+ coverages.push(coverage(CATEGORY, host, "not-applicable", `no hay bloque de proyecto para ${report.host} en ${input.workspaceDir}`));
53
+ continue;
54
+ }
55
+ coverages.push(coverage(CATEGORY, host, "checked"));
56
+ findings.push(...driftFindings(report));
57
+ }
58
+ // Y el complemento: un host participante que este motor NO sabe mirar
59
+ // declara `not-applicable`, no silencio. El motor sólo inspecciona
60
+ // claude/codex/warp; filtrar sin decirlo dejaba una máquina cuyo único host
61
+ // es gemini con CERO filas de esta categoría, y el agregador leía ese
62
+ // silencio como «no se pudo comprobar» y sacaba el doctor en 1 sobre un
63
+ // entorno sano. No mirar por diseño y no haber podido mirar son dos estados
64
+ // distintos, y sólo el segundo escala.
65
+ const covered = new Set(coverages.map((entry) => entry.host));
66
+ const uncovered = input.hosts
67
+ .filter((host) => !covered.has(host.host))
68
+ .map((host) => coverage(CATEGORY, host.host, "not-applicable", `la visibilidad multiroot no se declara para ${host.label}: sólo Claude Code, Codex y Warp registran rutas del workspace`));
69
+ return { coverage: [...dedupe(coverages), ...uncovered], findings };
70
+ },
71
+ };
72
+ function driftFindings(report) {
73
+ const resource = `${report.scope}:visibilidad`;
74
+ const host = catalogHost(report.host);
75
+ const base = {
76
+ host,
77
+ category: CATEGORY,
78
+ resource: {
79
+ kind: "visibility",
80
+ name: `${host} (${report.scope})`,
81
+ locator: report.target,
82
+ },
83
+ ownership: "ours",
84
+ };
85
+ const findings = [];
86
+ if (report.missing.length > 0) {
87
+ findings.push({
88
+ ...base,
89
+ id: doctorFindingId(host, CATEGORY, `${resource}:faltantes`),
90
+ state: "warning",
91
+ summary: `${host} no tiene registradas ${report.missing.length} ruta(s) declarada(s)`,
92
+ impact: "el host no ve esas fuentes: sus lecturas y ediciones ahí fallan",
93
+ evidence: report.missing.map((path) => `falta: ${path}`),
94
+ remediation: { kind: "manual", action: null, guidance: ["aw attach-multiroot"] },
95
+ proposal: { op: "multiroot.attach", args: { scope: report.scope } },
96
+ });
97
+ }
98
+ if (report.extra.length > 0) {
99
+ findings.push({
100
+ ...base,
101
+ id: doctorFindingId(host, CATEGORY, `${resource}:sobrantes`),
102
+ state: "warning",
103
+ summary: `${host} tiene registradas ${report.extra.length} ruta(s) que nadie declaró`,
104
+ impact: "el host ve directorios fuera del workspace declarado",
105
+ evidence: report.extra.map((path) => `sobra: ${path}`),
106
+ remediation: { kind: "manual", action: null, guidance: ["aw detach-multiroot"] },
107
+ proposal: { op: "multiroot.detach", args: { scope: report.scope } },
108
+ });
109
+ }
110
+ if (report.status === "no-settings") {
111
+ findings.push({
112
+ ...base,
113
+ id: doctorFindingId(host, CATEGORY, `${resource}:sin-config`),
114
+ state: "warning",
115
+ summary: `${host} no tiene el archivo de configuración que declara sus rutas`,
116
+ impact: "el host no ve ninguna fuente declarada del workspace",
117
+ evidence: [`archivo esperado: ${report.target}`],
118
+ remediation: { kind: "manual", action: null, guidance: ["aw attach-multiroot"] },
119
+ proposal: { op: "multiroot.attach", args: { scope: report.scope } },
120
+ });
121
+ }
122
+ if (findings.length === 0) {
123
+ findings.push({
124
+ ...base,
125
+ id: doctorFindingId(host, CATEGORY, resource),
126
+ state: "healthy",
127
+ summary: `${host} tiene registradas exactamente las rutas declaradas (${report.scope})`,
128
+ impact: "el host ve el workspace completo y nada más",
129
+ evidence: [
130
+ `archivo: ${report.target}`,
131
+ `rutas registradas: ${report.registered_paths.length}`,
132
+ ],
133
+ remediation: { kind: "none", action: null, guidance: [] },
134
+ });
135
+ }
136
+ return findings;
137
+ }
138
+ // A host inspected in both scopes reports its coverage twice; the report keeps
139
+ // one row per category and host.
140
+ function dedupe(entries) {
141
+ const byHost = new Map();
142
+ for (const entry of entries) {
143
+ const held = byHost.get(entry.host);
144
+ if (held === undefined || held.state === "not-applicable")
145
+ byHost.set(entry.host, entry);
146
+ }
147
+ return [...byHost.values()];
148
+ }
149
+ //# sourceMappingURL=provider-visibility.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"provider-visibility.js","sourceRoot":"","sources":["../../../src/application/doctor/provider-visibility.ts"],"names":[],"mappings":"AAAA,OAAO,EAAsB,eAAe,EAAE,MAAM,8BAA8B,CAAC;AACnF;;;;;;;;;;;;GAYG;AACH,OAAO,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;AAC9D,OAAO,EAAE,mBAAmB,EAAE,MAAM,iCAAiC,CAAC;AAGtE,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAEtC,MAAM,QAAQ,GAAG,sBAA+B,CAAC;AAEjD;;;;;;GAMG;AACH,SAAS,WAAW,CAAC,IAAY;IAC/B,OAAO,iBAAiB,CAAC,IAA+C,CAAC,EAAE,EAAE,IAAI,IAAI,CAAC;AACxF,CAAC;AAED,MAAM,CAAC,MAAM,kBAAkB,GAAmB;IAChD,QAAQ,EAAE,QAAQ;IAClB,KAAK,CAAC,GAAG,CAAC,KAA0B;QAClC,MAAM,MAAM,GAAG,MAAM,mBAAmB,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,EAAE,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,CAAC,GAAG,CAAC,KAAK,EAAE;YACrF,SAAS,EAAE,KAAK,CAAC,YAAY;YAC7B,MAAM,EAAE,IAAI;SACb,CAAC,CAAC;QACH,MAAM,OAAO,GAAG,CAAC,GAAG,MAAM,CAAC,OAAO,EAAE,GAAG,MAAM,CAAC,cAAc,CAAC,CAAC;QAC9D,MAAM,QAAQ,GAAoB,EAAE,CAAC;QACrC,MAAM,SAAS,GAAkC,EAAE,CAAC;QACpD,4EAA4E;QAC5E,6EAA6E;QAC7E,4EAA4E;QAC5E,6EAA6E;QAC7E,4EAA4E;QAC5E,2EAA2E;QAC3E,YAAY;QACZ,MAAM,YAAY,GAAG,IAAI,GAAG,CAAS,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;QAE3E,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;YAC7B,MAAM,IAAI,GAAG,WAAW,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;YACtC,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,IAAI,CAAC;gBAAE,SAAS;YACtC,IAAI,MAAM,CAAC,MAAM,KAAK,kBAAkB,EAAE,CAAC;gBACzC,SAAS,CAAC,IAAI,CACZ,QAAQ,CACN,QAAQ,EACR,IAAI,EACJ,gBAAgB,EAChB,kCAAkC,MAAM,CAAC,IAAI,OAAO,KAAK,CAAC,YAAY,EAAE,CACzE,CACF,CAAC;gBACF,SAAS;YACX,CAAC;YACD,SAAS,CAAC,IAAI,CAAC,QAAQ,CAAC,QAAQ,EAAE,IAAI,EAAE,SAAS,CAAC,CAAC,CAAC;YACpD,QAAQ,CAAC,IAAI,CAAC,GAAG,aAAa,CAAC,MAAM,CAAC,CAAC,CAAC;QAC1C,CAAC;QAED,sEAAsE;QACtE,mEAAmE;QACnE,4EAA4E;QAC5E,sEAAsE;QACtE,wEAAwE;QACxE,4EAA4E;QAC5E,uCAAuC;QACvC,MAAM,OAAO,GAAG,IAAI,GAAG,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC;QAC9D,MAAM,SAAS,GAAG,KAAK,CAAC,KAAK;aAC1B,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;aACzC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CACZ,QAAQ,CACN,QAAQ,EACR,IAAI,CAAC,IAAI,EACT,gBAAgB,EAChB,+CAA+C,IAAI,CAAC,KAAK,gEAAgE,CAC1H,CACF,CAAC;QAEJ,OAAO,EAAE,QAAQ,EAAE,CAAC,GAAG,MAAM,CAAC,SAAS,CAAC,EAAE,GAAG,SAAS,CAAC,EAAE,QAAQ,EAAE,CAAC;IACtE,CAAC;CACF,CAAC;AAEF,SAAS,aAAa,CAAC,MAA4B;IACjD,MAAM,QAAQ,GAAG,GAAG,MAAM,CAAC,KAAK,cAAc,CAAC;IAC/C,MAAM,IAAI,GAAG,WAAW,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IACtC,MAAM,IAAI,GAAG;QACX,IAAI;QACJ,QAAQ,EAAE,QAAQ;QAClB,QAAQ,EAAE;YACR,IAAI,EAAE,YAAqB;YAC3B,IAAI,EAAE,GAAG,IAAI,KAAK,MAAM,CAAC,KAAK,GAAG;YACjC,OAAO,EAAE,MAAM,CAAC,MAAM;SACvB;QACD,SAAS,EAAE,MAAe;KAC3B,CAAC;IACF,MAAM,QAAQ,GAAoB,EAAE,CAAC;IACrC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC9B,QAAQ,CAAC,IAAI,CAAC;YACZ,GAAG,IAAI;YACP,EAAE,EAAE,eAAe,CAAC,IAAI,EAAE,QAAQ,EAAE,GAAG,QAAQ,YAAY,CAAC;YAC5D,KAAK,EAAE,SAAS;YAChB,OAAO,EAAE,GAAG,IAAI,yBAAyB,MAAM,CAAC,OAAO,CAAC,MAAM,uBAAuB;YACrF,MAAM,EAAE,iEAAiE;YACzE,QAAQ,EAAE,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,UAAU,IAAI,EAAE,CAAC;YACxD,WAAW,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC,qBAAqB,CAAC,EAAE;YAChF,QAAQ,EAAE,EAAE,EAAE,EAAE,kBAAkB,EAAE,IAAI,EAAE,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,EAAE;SACpE,CAAC,CAAC;IACL,CAAC;IACD,IAAI,MAAM,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC5B,QAAQ,CAAC,IAAI,CAAC;YACZ,GAAG,IAAI;YACP,EAAE,EAAE,eAAe,CAAC,IAAI,EAAE,QAAQ,EAAE,GAAG,QAAQ,YAAY,CAAC;YAC5D,KAAK,EAAE,SAAS;YAChB,OAAO,EAAE,GAAG,IAAI,sBAAsB,MAAM,CAAC,KAAK,CAAC,MAAM,4BAA4B;YACrF,MAAM,EAAE,sDAAsD;YAC9D,QAAQ,EAAE,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,UAAU,IAAI,EAAE,CAAC;YACtD,WAAW,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC,qBAAqB,CAAC,EAAE;YAChF,QAAQ,EAAE,EAAE,EAAE,EAAE,kBAAkB,EAAE,IAAI,EAAE,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,EAAE;SACpE,CAAC,CAAC;IACL,CAAC;IACD,IAAI,MAAM,CAAC,MAAM,KAAK,aAAa,EAAE,CAAC;QACpC,QAAQ,CAAC,IAAI,CAAC;YACZ,GAAG,IAAI;YACP,EAAE,EAAE,eAAe,CAAC,IAAI,EAAE,QAAQ,EAAE,GAAG,QAAQ,aAAa,CAAC;YAC7D,KAAK,EAAE,SAAS;YAChB,OAAO,EAAE,GAAG,IAAI,6DAA6D;YAC7E,MAAM,EAAE,sDAAsD;YAC9D,QAAQ,EAAE,CAAC,qBAAqB,MAAM,CAAC,MAAM,EAAE,CAAC;YAChD,WAAW,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC,qBAAqB,CAAC,EAAE;YAChF,QAAQ,EAAE,EAAE,EAAE,EAAE,kBAAkB,EAAE,IAAI,EAAE,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,EAAE;SACpE,CAAC,CAAC;IACL,CAAC;IACD,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC1B,QAAQ,CAAC,IAAI,CAAC;YACZ,GAAG,IAAI;YACP,EAAE,EAAE,eAAe,CAAC,IAAI,EAAE,QAAQ,EAAE,QAAQ,CAAC;YAC7C,KAAK,EAAE,SAAS;YAChB,OAAO,EAAE,GAAG,IAAI,wDAAwD,MAAM,CAAC,KAAK,GAAG;YACvF,MAAM,EAAE,6CAA6C;YACrD,QAAQ,EAAE;gBACR,YAAY,MAAM,CAAC,MAAM,EAAE;gBAC3B,sBAAsB,MAAM,CAAC,gBAAgB,CAAC,MAAM,EAAE;aACvD;YACD,WAAW,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE,EAAE;SAC1D,CAAC,CAAC;IACL,CAAC;IACD,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED,+EAA+E;AAC/E,iCAAiC;AACjC,SAAS,MAAM,CAAC,OAA+C;IAC7D,MAAM,MAAM,GAAG,IAAI,GAAG,EAAuC,CAAC;IAC9D,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;QAC5B,MAAM,IAAI,GAAG,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QACpC,IAAI,IAAI,KAAK,SAAS,IAAI,IAAI,CAAC,KAAK,KAAK,gBAAgB;YAAE,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;IAC3F,CAAC;IACD,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC;AAC9B,CAAC"}
@@ -0,0 +1,169 @@
1
+ import { readMcpConnections } from "../mcp-connections-service.js";
2
+ import { runMcpMigration } from "../mcp-migration-service.js";
3
+ import { runMcpRemove } from "../mcp-remove-service.js";
4
+ import { runMcpSetup } from "../mcp-setup-service.js";
5
+ import { runMultiroot } from "../multiroot-service.js";
6
+ import { selfCleanLegacy } from "../self/clean-legacy.js";
7
+ import { selfInstallHooks } from "../self/install-hooks.js";
8
+ import { selfInstallSkill } from "../self/install-skill.js";
9
+ import { reinstallSkill } from "../self/skills-manager.js";
10
+ import { selfUninstall } from "../self/uninstall.js";
11
+ import { runDoctorAuthFlow } from "./auth-flow.js";
12
+ /** Un `ParsedArgs` explícito: sin comando, sin positional y con los flags que la operación pide. */
13
+ function argsOf(values, flags = []) {
14
+ return {
15
+ rest: [],
16
+ plugin: {},
17
+ flags: new Set(flags),
18
+ values: new Map(Object.entries(values)),
19
+ valuesMulti: new Map(),
20
+ };
21
+ }
22
+ /** Un `CommandResult` se traduce por su `ok` y su `exitCode`, nunca por su prosa. */
23
+ function fromCommand(result, what) {
24
+ if (result.ok)
25
+ return { status: "applied", detail: `${what}: aplicado` };
26
+ const code = result.error?.code ?? "sin código";
27
+ // `blocked` es el estado que `install-hooks` usa cuando una entrada inválida
28
+ // de la persona desarmaría la sección: no es un fallo del doctor, es una
29
+ // negativa deliberada de la operación, y se respeta como tal.
30
+ const blocked = code === "HOOKS_BLOCKED" || code.endsWith("_BLOCKED");
31
+ return {
32
+ status: blocked ? "blocked" : "failed",
33
+ detail: `${what}: ${code}`,
34
+ };
35
+ }
36
+ export async function runDoctorRepair(action, ctx) {
37
+ switch (action.op) {
38
+ case "self.install-skill":
39
+ return fromCommand(await selfInstallSkill(argsOf({ target: target(action) }), ctx), "instalar el bundle");
40
+ case "self.uninstall":
41
+ return fromCommand(await selfUninstall(argsOf({ target: target(action) }), ctx), "retirar la configuración residual");
42
+ case "self.install-hooks":
43
+ return fromCommand(await selfInstallHooks(argsOf({ target: target(action) }), ctx), "armar los hooks");
44
+ case "self.clean-legacy":
45
+ return fromCommand(await selfCleanLegacy(argsOf({ target: target(action) }), ctx), "limpiar el resto legacy");
46
+ case "skills.reinstall":
47
+ return fromCommand(await reinstallSkill(ctx, action.args.name ?? ""), "reinstalar la skill");
48
+ case "mcp.setup":
49
+ return mcpOutcome(runMcpSetup(ctx.env, mcpInput(ctx, action)), "registrar la entrada MCP");
50
+ case "mcp.remove":
51
+ return mcpOutcome(runMcpRemove(ctx.env, mcpInput(ctx, action)), "retirar la entrada MCP");
52
+ case "mcp.migrate":
53
+ return migrateOutcome(action, ctx);
54
+ case "auth.flow":
55
+ return runDoctorAuthFlow(action, ctx);
56
+ case "multiroot.attach":
57
+ case "multiroot.detach":
58
+ return multirootOutcome(await runMultiroot(ctx.fs, ctx.env, ctx.paths, action.op === "multiroot.attach" ? "attach" : "detach", {
59
+ fromSources: true,
60
+ useGlobal: action.args.scope === "global",
61
+ }));
62
+ default:
63
+ // Una operación que el catálogo declara y este adaptador no sabe correr es
64
+ // un fallo declarado, no un `applied` optimista: el recurso queda como
65
+ // estaba y la recomprobación lo va a confirmar.
66
+ return { status: "failed", detail: `no hay adaptador para la operación '${action.op}'` };
67
+ }
68
+ }
69
+ function target(action) {
70
+ return (action.args.target ?? "");
71
+ }
72
+ /**
73
+ * El scope global exige su propia autorización, y acá la hay.
74
+ *
75
+ * `globalApproval: "explicit-self-action"` es exactamente lo que este caso es:
76
+ * una persona leyó la vista previa de este lote y aprobó su digest. Sin ese
77
+ * valor `runMcpSetup` se niega a escribir una configuración global, y con razón
78
+ * — tocarla afecta todos los proyectos de la máquina.
79
+ */
80
+ function mcpInput(ctx, action) {
81
+ const host = (action.args.host ?? "");
82
+ const instance = action.args.instance ?? "";
83
+ const connections = readMcpConnections(ctx.paths, ctx.env).filter((connection) => connection.name === instance);
84
+ const scope = action.args.scope === "global" ? "global" : "workspace";
85
+ return {
86
+ hosts: [host],
87
+ connections,
88
+ ...(scope === "global"
89
+ ? { scope: "global", globalApproval: "explicit-self-action" }
90
+ : { scope: "workspace", workspace: ctx.paths.workspaceDir() }),
91
+ };
92
+ }
93
+ /**
94
+ * Los servicios de MCP no lanzan: acumulan y devuelven parcial.
95
+ *
96
+ * Así que el resultado se lee de sus listas, no de una excepción. Un conflicto
97
+ * es una entrada ajena homónima que el servicio PRESERVÓ, y eso no es un fallo
98
+ * del doctor sino la protección funcionando: se reporta como fallo de la acción
99
+ * porque el recurso no quedó reparado, con la razón a la vista.
100
+ */
101
+ function mcpOutcome(result, what) {
102
+ if ("error" in result)
103
+ return { status: "failed", detail: `${what}: ${result.error}` };
104
+ const conflicts = result.conflicts?.length ?? 0;
105
+ const errors = result.errors?.length ?? 0;
106
+ if (conflicts > 0) {
107
+ return {
108
+ status: "blocked",
109
+ detail: `${what}: hay una entrada homónima ajena y se preservó`,
110
+ };
111
+ }
112
+ if (errors > 0)
113
+ return { status: "failed", detail: `${what}: ${errors} error(es) del host` };
114
+ const applied = result.applied?.length ?? 0;
115
+ return applied > 0
116
+ ? { status: "applied", detail: `${what}: aplicado` }
117
+ : { status: "failed", detail: `${what}: el servicio no escribió nada` };
118
+ }
119
+ /**
120
+ * La migración habla otro idioma: sus pasos son `items` con su propia acción.
121
+ *
122
+ * Y su `apply` es explícito — sin él la corrida es una vista previa. El doctor
123
+ * ya trae la aprobación del digest, así que acá aplica; el `globalApproval` de
124
+ * este servicio es un booleano y no la etiqueta que usa `setup`, otro caso donde
125
+ * dos servicios hermanos no comparten firma y el adaptador es el único lugar que
126
+ * lo sabe.
127
+ */
128
+ function migrateOutcome(action, ctx) {
129
+ const host = (action.args.host ?? "");
130
+ const instance = action.args.instance ?? "";
131
+ const connections = readMcpConnections(ctx.paths, ctx.env).filter((connection) => connection.name === instance);
132
+ const result = action.args.scope === "global"
133
+ ? runMcpMigration(ctx.env, {
134
+ scope: "global",
135
+ hosts: [host],
136
+ connections,
137
+ apply: true,
138
+ globalApproval: true,
139
+ })
140
+ : runMcpMigration(ctx.env, {
141
+ scope: "workspace",
142
+ workspace: ctx.paths.workspaceDir(),
143
+ hosts: [host],
144
+ connections,
145
+ apply: true,
146
+ });
147
+ if ("error" in result)
148
+ return { status: "failed", detail: `migrar: ${String(result.error)}` };
149
+ const blocked = result.items.filter((item) => item.action === "blocked");
150
+ if (blocked.length > 0) {
151
+ return { status: "blocked", detail: "migrar: el servicio declaró el paso bloqueado" };
152
+ }
153
+ const failed = result.items.filter((item) => item.action === "failed");
154
+ if (failed.length > 0)
155
+ return { status: "failed", detail: "migrar: el servicio reportó un fallo" };
156
+ const moved = result.items.filter((item) => item.action === "install" || item.action === "replace-known-legacy");
157
+ return moved.length > 0
158
+ ? { status: "applied", detail: "migrar: aplicado" }
159
+ : { status: "failed", detail: "migrar: no había nada que mover" };
160
+ }
161
+ function multirootOutcome(result) {
162
+ const error = result !== null && typeof result === "object" && "error" in result
163
+ ? String(result.error)
164
+ : null;
165
+ return error === null
166
+ ? { status: "applied", detail: "visibilidad multiroot: aplicada" }
167
+ : { status: "failed", detail: `visibilidad multiroot: ${error}` };
168
+ }
169
+ //# sourceMappingURL=repair-runner.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"repair-runner.js","sourceRoot":"","sources":["../../../src/application/doctor/repair-runner.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,155 @@
1
+ /**
2
+ * The aggregate: run every provider, consolidate, redact, and let the verdict
3
+ * fall out of what was found.
4
+ *
5
+ * A provider that throws does not take the report down with it. Its category
6
+ * becomes `unavailable` WITH THE REASON on every participating host, and the
7
+ * verdict turns non-zero — because a doctor that answers 0 after failing to look
8
+ * is read as "healthy", which is the one thing it must never say by accident.
9
+ */
10
+ import { DOCTOR_SCHEMA_VERSION, doctorFindingId, doctorVerdict, sortDoctorCoverage, sortDoctorFindings, summarizeDoctorFindings, } from "../../domain/doctor/model.js";
11
+ import { redactSensitiveValue } from "../../domain/redaction.js";
12
+ import { readPackageVersion } from "../../runtime/version.js";
13
+ import { annotateRepairs } from "./actions.js";
14
+ import { DOCTOR_HOST_ORDER, selectDoctorHosts } from "./hosts.js";
15
+ import { installationProvider } from "./provider-installation.js";
16
+ import { createMcpsProvider } from "./provider-mcps.js";
17
+ import { pluginsHooksProvider } from "./provider-plugins-hooks.js";
18
+ import { skillsProvider } from "./provider-skills.js";
19
+ import { toolsAuthProvider } from "./provider-tools-auth.js";
20
+ import { visibilityProvider } from "./provider-visibility.js";
21
+ import { coverage } from "./types.js";
22
+ export function defaultDoctorProviders(deps = {}) {
23
+ return [
24
+ installationProvider,
25
+ createMcpsProvider(deps),
26
+ skillsProvider,
27
+ toolsAuthProvider,
28
+ pluginsHooksProvider,
29
+ visibilityProvider,
30
+ ];
31
+ }
32
+ export async function runDoctor(ctx, options = {}, deps = {}) {
33
+ const currentHost = normalizeHost(options.host ?? null);
34
+ const only = [...(options.only ?? [])];
35
+ const selection = await selectDoctorHosts(ctx, { currentHost, only });
36
+ const workspaceDir = ctx.paths.workspaceDir();
37
+ const input = {
38
+ ctx,
39
+ hosts: selection.hosts,
40
+ hostStates: selection.states,
41
+ currentHost,
42
+ workspaceDir,
43
+ skipNative: options.skipNative === true,
44
+ ...(options.verify === undefined ? {} : { verifyAuthorization: options.verify }),
45
+ };
46
+ // Un `--only` mal escrito se denuncia ANTES de mirar nada: es lo único que
47
+ // explica por qué la corrida no cubrió lo que la persona creía haber pedido.
48
+ const findings = selection.unknownOnly.map(unknownOnlyFinding);
49
+ const coverages = [];
50
+ for (const provider of deps.providers ?? defaultDoctorProviders(deps.mcps)) {
51
+ try {
52
+ const output = await provider.run(input);
53
+ findings.push(...output.findings);
54
+ // Silencio NO es cobertura: una categoría que desaparece del informe deja
55
+ // a `doctorVerdict` sin nada que ver y el doctor sale 0 después de no
56
+ // haber mirado (AC-02, AC-14). Pero el estado del relleno es
57
+ // `not-applicable`, NO `unavailable`: un proveedor calla cuando la corrida
58
+ // no le dio ningún host que le corresponda —una máquina sin ningún host de
59
+ // agente, o un `--only` que no dejó participantes—, y eso es «no había
60
+ // nada que comprobar», no «no se pudo comprobar». Marcarlo caído ponía en
61
+ // rojo un entorno sano; lo que sí escala es el proveedor que LANZA, abajo,
62
+ // y un `--only` que nombra lo que el catálogo no declara, que ya viaja
63
+ // como hallazgo bloqueante.
64
+ coverages.push(...(output.coverage.length > 0
65
+ ? output.coverage
66
+ : [coverage(provider.category, "workspace", "not-applicable", silenceReason(selection))]));
67
+ }
68
+ catch (error) {
69
+ const reason = `el proveedor falló: ${messageOf(error)}`;
70
+ const scopes = selection.hosts.length > 0 ? selection.hosts.map((h) => h.host) : ["workspace"];
71
+ for (const host of scopes) {
72
+ coverages.push(coverage(provider.category, host, "unavailable", reason));
73
+ }
74
+ }
75
+ }
76
+ const hostOrder = [...DOCTOR_HOST_ORDER, "workspace"];
77
+ // El gate de propiedad, en un solo lugar y sobre el conjunto consolidado: acá
78
+ // es donde una sugerencia de proveedor se vuelve acción, o no. También es
79
+ // donde `proposal` se consume: el informe emitido no la lleva.
80
+ const orderedFindings = sortDoctorFindings(annotateRepairs(findings), hostOrder);
81
+ const orderedCoverage = sortDoctorCoverage(coverages, hostOrder);
82
+ const report = {
83
+ schema_version: DOCTOR_SCHEMA_VERSION,
84
+ cli_version: readPackageVersion(),
85
+ scope: { workspace_dir: workspaceDir, current_host: currentHost, only },
86
+ hosts: selection.hosts.map(({ mcp_host: _mcpHost, ...view }) => view),
87
+ hosts_absent: selection.absent,
88
+ coverage: orderedCoverage,
89
+ findings: orderedFindings,
90
+ summary: summarizeDoctorFindings(orderedFindings),
91
+ verdict: doctorVerdict(orderedFindings, orderedCoverage),
92
+ };
93
+ // Last line of defence, over the WHOLE object and once: the same redaction the
94
+ // CLI already applies to every command's output. A provider that reads a file
95
+ // holding a credential cannot leak it past here.
96
+ return redactSensitiveValue(report);
97
+ }
98
+ /**
99
+ * Por qué una categoría no declaró ni una fila de cobertura.
100
+ *
101
+ * Las tres razones son distintas y la persona necesita la suya: un filtro que
102
+ * no existe, una selección sin hosts, o un proveedor que miró y no dijo nada.
103
+ */
104
+ function silenceReason(selection) {
105
+ if (selection.unknownOnly.length > 0) {
106
+ return `no había nada que comprobar: --only nombra hosts que el catálogo no declara (${selection.unknownOnly.join(", ")})`;
107
+ }
108
+ if (selection.hosts.length === 0) {
109
+ return "no había nada que comprobar: ningún host participa en esta corrida";
110
+ }
111
+ return "el proveedor no declaró cobertura para ningún host de la corrida";
112
+ }
113
+ /**
114
+ * El hallazgo de un `--only` que el catálogo no conoce.
115
+ *
116
+ * Bloqueante a propósito: la corrida NO miró el host que se pidió, y un cero
117
+ * ahí se lee como «tu entorno está sano». La sugerencia sale del catálogo, así
118
+ * que `--only claude` apunta a `claude-code` sin que este módulo cablee ningún
119
+ * alias propio.
120
+ */
121
+ function unknownOnlyFinding(name) {
122
+ const near = DOCTOR_HOST_ORDER.filter((id) => id.includes(name) || name.includes(id));
123
+ const evidence = [`el catálogo declara: ${DOCTOR_HOST_ORDER.join(", ")}`];
124
+ if (near.length > 0)
125
+ evidence.push(`el nombre más parecido del catálogo: ${near.join(", ")}`);
126
+ return {
127
+ id: doctorFindingId("workspace", "installation-hosts", `--only=${name}`),
128
+ host: "workspace",
129
+ category: "installation-hosts",
130
+ resource: { kind: "filtro", name: `--only=${name}`, locator: null },
131
+ state: "blocking",
132
+ summary: `--only nombra «${name}», que el catálogo de hosts no declara`,
133
+ impact: "el diagnóstico no cubrió el host que pediste y no puede hablar por él",
134
+ evidence,
135
+ ownership: "n/a",
136
+ remediation: {
137
+ kind: "manual",
138
+ action: null,
139
+ guidance: [
140
+ near.length > 0
141
+ ? `repetí la corrida con --only ${near[0]}`
142
+ : "repetí la corrida con un id del catálogo, o sin --only para mirar todos los hosts",
143
+ ],
144
+ },
145
+ };
146
+ }
147
+ function normalizeHost(host) {
148
+ if (host === null)
149
+ return null;
150
+ return DOCTOR_HOST_ORDER.includes(host) ? host : null;
151
+ }
152
+ function messageOf(error) {
153
+ return error instanceof Error ? error.message : String(error);
154
+ }
155
+ //# sourceMappingURL=report.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"report.js","sourceRoot":"","sources":["../../../src/application/doctor/report.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ /** `checked` carries no reason; everything else must say why. */
2
+ export function coverage(category, host, state, reason = null) {
3
+ return { category, host, state, reason };
4
+ }
5
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/application/doctor/types.ts"],"names":[],"mappings":"AAgEA,iEAAiE;AACjE,MAAM,UAAU,QAAQ,CACtB,QAAwB,EACxB,IAAY,EACZ,KAA8B,EAC9B,SAAwB,IAAI;IAE5B,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC;AAC3C,CAAC"}
@@ -30,24 +30,31 @@ import { authorizeTransition, effectApprovalDigest, } from "../../domain/flow/au
30
30
  import { PAUSE_LABEL, STOP_LABEL, buildFlowDirective, skippedStepOf, stepOf, } from "../../domain/flow/directive.js";
31
31
  import { ROUTE_ACCEPT_LABEL, ROUTE_ADJUST_LABEL, dispositionOf } from "../../domain/flow/route.js";
32
32
  import { bindAction, docsBoundaryBreach, skipReason, } from "../../domain/flow/rules.js";
33
- import { MAX_BOUNDARY_ATTEMPTS, applyTransition, attemptAccountingAt, attemptsAt, checkAgainstJourney, currentBatchIteration, degradeTransition, positionDigest, skipTransition, withBoundary, withPendingAction, withPlanExecBatchStageForTransition, } from "../../domain/flow/run-state.js";
33
+ import { MAX_BOUNDARY_ATTEMPTS, applyAttemptReconciliation, applyTransition, attemptAccountingAt, attemptsAt, checkAgainstJourney, currentBatchIteration, degradeTransition, positionDigest, reconcileAttemptsAt, skipTransition, withBoundary, withPendingAction, withPlanExecBatchStageForTransition, } from "../../domain/flow/run-state.js";
34
34
  import { buildSemanticRequest, semanticDigest, } from "../semantic-operation/protocol.js";
35
35
  import { sessionSlug } from "../session-resolver.js";
36
36
  export function advanceFlowRun(input) {
37
37
  const incoherent = checkAgainstJourney(input.state, input.journey);
38
38
  if (incoherent !== null)
39
39
  return { ok: false, failure: incoherent };
40
+ // The run repairs its OWN bookkeeping first, and only when the mismatch has
41
+ // exactly one reading. No boundary is opened, no attempt is charged, no
42
+ // degradation is asked for and nothing is added to the directive: what the
43
+ // caller sees is the boundary they were going to see anyway, answerable. The
44
+ // repair survives as a line in the run's sealed trace, which is what makes
45
+ // "it is not notified" different from "it cannot be audited".
46
+ const reconciled = reconcileRun(input.state, input.journey);
40
47
  // A handoff is terminal for this run even though the linear journey still has
41
48
  // rows after the gate. Letting `advance` walk those rows would build the
42
49
  // escalation package and then continue toward commits, which is exactly the
43
50
  // contradictory route the typed choice closes.
44
- if (input.state.handoff !== null && input.state.handoff !== undefined) {
45
- const stopped = input.journey[input.state.applied.length] ?? null;
46
- let state = withBoundary(input.state, stopped?.id ?? null);
51
+ if (reconciled.handoff !== null && reconciled.handoff !== undefined) {
52
+ const stopped = input.journey[reconciled.applied.length] ?? null;
53
+ let state = withBoundary(reconciled, stopped?.id ?? null);
47
54
  state = withPendingAction(state, null);
48
55
  return directiveFor(state, resolveBoundary(state, input.journey), input.applied ?? []);
49
56
  }
50
- const walked = walk(input.state, input.journey);
57
+ const walked = walk(reconciled, input.journey);
51
58
  let state = walked.state;
52
59
  const appliedNow = [...(input.applied ?? []), ...walked.applied];
53
60
  // The boundary and the pending action are written into the state BEFORE its seal
@@ -68,6 +75,19 @@ export function advanceFlowRun(input) {
68
75
  : { transition: stopped.id, digest: actionDigest(emitted) });
69
76
  return directiveFor(state, resolveBoundary(state, input.journey), appliedNow);
70
77
  }
78
+ /**
79
+ * Reconcile the boundary the run is standing at, or hand the state back untouched.
80
+ *
81
+ * It asks the domain and applies nothing of its own: which mismatches have one
82
+ * reading is {@link reconcileAttemptsAt}'s call, and repairing-plus-recording is
83
+ * a single function so a repair can never happen unrecorded.
84
+ */
85
+ function reconcileRun(state, journey) {
86
+ const stopped = journey[state.applied.length] ?? null;
87
+ if (stopped === null)
88
+ return state;
89
+ return applyAttemptReconciliation(state, reconcileAttemptsAt(state, stopped.id));
90
+ }
71
91
  /**
72
92
  * Every consecutive transition the CLI owns AND can apply itself, in one go.
73
93
  *
@@ -253,13 +273,10 @@ function blockedCause(state, stopped, emitted) {
253
273
  // matters: one continues on the next advance, the other cannot. Reporting the
254
274
  // stricter message for both would tell somebody their run is stuck when it is
255
275
  // about to move on.
256
- const degradable = exhaustionSkip(state, stopped) !== null;
257
276
  return {
258
277
  code: "FLOW_BOUNDARY_EXHAUSTED",
259
278
  message: `'${stopped.id}' agotó sus ${MAX_BOUNDARY_ATTEMPTS} intentos: contestarlo otra vez sería el bucle que la regla evita${conflictClause(accounting)}`,
260
- action: degradable
261
- ? `${DEGRADE_ACTION}; el próximo 'aw flow advance' lo pasa por alto dejando dicho por qué y sigue con el resto`
262
- : `${DEGRADE_ACTION}, y resolvé este paso fuera de la corrida antes de seguir: saltearlo daría por aprobado un efecto que nadie aprobó, o por hecho algo que nada corrió. Si lo que hay que rehacer es el intento, '${recoverInvocation(state)}' le devuelve los intentos a esta frontera conservando todo lo aplicado`,
279
+ action: exhaustedAction(state, stopped),
263
280
  };
264
281
  }
265
282
  // Not exhausted, and still not answerable: the rows this run persisted cannot
@@ -281,6 +298,35 @@ function blockedCause(state, stopped, emitted) {
281
298
  }
282
299
  return null;
283
300
  }
301
+ /**
302
+ * What to do about an exhausted boundary — decided by WHAT is broken.
303
+ *
304
+ * Degrading the gap is the chassis' destination for a gap that cannot close, and
305
+ * it was read first no matter the cause: with the accounting in conflict it
306
+ * repairs nothing, and being the first thing offered is what sent a mechanical
307
+ * mismatch to a refinement nobody needed. A conflict that reaches here is one the
308
+ * automatic reconciliation refused because its reading is NOT unique, so the exit
309
+ * is the verb that recovers — and degradation is not offered at all, because the
310
+ * gap is not the problem.
311
+ */
312
+ function exhaustedAction(state, stopped) {
313
+ // Reaching here means the automatic reconciliation did NOT repair the mismatch,
314
+ // so what is left is a count nobody can reconstruct. Not every unrepaired
315
+ // mismatch belongs to the accounting: a boundary that already moved the world
316
+ // is refused BY the recovery too, and there degrading the gap is the honest
317
+ // exit. That is why the scope decides, and not the mere presence of a conflict
318
+ // — a forgiven counter excess keeps reporting its difference forever, and
319
+ // reading that as "broken accounting" would deny degradation to a boundary
320
+ // whose cap was reached legitimately.
321
+ const ambiguous = reconcileAttemptsAt(state, stopped.id).ambiguous;
322
+ if (ambiguous?.scope === "accounting") {
323
+ return `lo roto es la contabilidad de la corrida, no el gap: ${ambiguous.reason}. '${recoverInvocation(state)}' le devuelve los intentos a esta frontera conservando todo lo aplicado; degradar el gap no arregla una cuenta`;
324
+ }
325
+ if (exhaustionSkip(state, stopped) !== null) {
326
+ return `${DEGRADE_ACTION}; el próximo 'aw flow advance' lo pasa por alto dejando dicho por qué y sigue con el resto`;
327
+ }
328
+ return `${DEGRADE_ACTION}, y resolvé este paso fuera de la corrida antes de seguir: saltearlo daría por aprobado un efecto que nadie aprobó, o por hecho algo que nada corrió. Si lo que hay que rehacer es el intento, '${recoverInvocation(state)}' le devuelve los intentos a esta frontera conservando todo lo aplicado`;
329
+ }
284
330
  /**
285
331
  * The disagreeing representations, said inline — or nothing.
286
332
  *
@@ -372,6 +418,13 @@ function exhaustionSkip(state, decision) {
372
418
  return null;
373
419
  if (!owned(decision))
374
420
  return null;
421
+ // Degradation is what a GAP that cannot close deserves. When the exhaustion
422
+ // comes from a bookkeeping mismatch the automatic reconciliation could not
423
+ // read, passing the step over would skip real work because two counters
424
+ // disagree — the same mistake as sending it to a refinement, one notch worse.
425
+ // So it is not offered at all: the boundary blocks and names the recovery.
426
+ if (reconcileAttemptsAt(state, decision.id).ambiguous?.scope === "accounting")
427
+ return null;
375
428
  const failure = executionFailure(state, decision.id);
376
429
  if (actionOf(decision) !== null && failure === null)
377
430
  return null;