auriga-cli 1.18.4 → 1.18.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.
@@ -1,4 +1,10 @@
1
- export type ItemStatus = "installed" | "update-available" | "not-installed";
1
+ export type ItemStatus = "installed" | "update-available" | "not-installed"
2
+ /** Dual-Agent plugin where some target Agents have the plugin installed
3
+ * and some don't (e.g. Claude side installed, Codex side missing). Distinct
4
+ * from `update-available` because the action is "install on the missing
5
+ * side", not "upgrade to a newer version". The missing agents are
6
+ * enumerated in `PluginState.missingAgents`. */
7
+ | "partial-install";
2
8
  /**
3
9
  * Per-category scan scope. Each category (workflow / skills / plugins / hooks)
4
10
  * can be independently scanned in either user scope (~/.claude/, ~/.codex/)
@@ -53,9 +59,15 @@ export interface PluginState {
53
59
  * `agents.length === 2` the UI shows a BOTH badge and Apply installs to
54
60
  * each agent in turn. Status is aggregated across all targeted agents:
55
61
  * `installed` ⇔ all agents installed; `not-installed` ⇔ all not-installed;
56
- * any partial state (one side installed, other not) → `update-available`
57
- * so a single Apply backfills the missing side. */
62
+ * partial state (some installed, some not) → `partial-install`; agent-
63
+ * uniform version drift `update-available`. */
58
64
  agents: ApplyAgent[];
65
+ /** Agents that target this plugin but don't have it installed. Populated
66
+ * iff `status === "partial-install"`. Lets the UI render per-agent
67
+ * ✓/✗ marks and tell the user exactly which side needs `auriga-cli`
68
+ * to backfill. Always omitted for `installed` / `not-installed` /
69
+ * `update-available`. */
70
+ missingAgents?: ApplyAgent[];
59
71
  currentVersion?: string;
60
72
  expectedVersion?: string;
61
73
  versionSource: "upstream-live" | "catalog";
@@ -80,7 +92,12 @@ export interface HookState {
80
92
  observedScope?: ScanScope;
81
93
  }
82
94
  export interface StateWarning {
83
- code: "claude-cli-missing" | "codex-cli-missing" | "marketplace-offline" | "claude-code-not-installed" | "settings-unreadable" | "skill-malformed" | "workflow-unknown-version";
95
+ code: "claude-cli-missing" | "codex-cli-missing" | "marketplace-offline" | "claude-code-not-installed" | "settings-unreadable" | "skill-malformed"
96
+ /** Project-scope CLAUDE.md (or the user-scope fallback when scanning
97
+ * user scope) is present but has no recognizable `# auriga Workflow (vX.Y.Z)`
98
+ * header. The row reports `not-installed`; install will back up the
99
+ * existing file to `CLAUDE.md.bak` and write ours. */
100
+ | "workflow-foreign-claudemd";
84
101
  message: string;
85
102
  }
86
103
  export type ApplyCategory = "workflow" | "skill" | "recommended-skill" | "plugin" | "hook";
package/dist/catalog.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "generatedAt": "2026-05-12T15:14:34.188Z",
2
+ "generatedAt": "2026-05-12T16:35:23.439Z",
3
3
  "workflowVersion": "1.7.0",
4
4
  "workflowSkills": [
5
5
  {
package/dist/state.d.ts CHANGED
@@ -71,23 +71,6 @@ export interface ScanOptions {
71
71
  homeDir?: string;
72
72
  }
73
73
  export declare function scanState(projectRoot: string, catalog: Catalog, opts?: ScanOptions): Promise<StateReport>;
74
- /**
75
- * Dedupe plugins by `id`, merging dual-Agent records into a single
76
- * multi-agent row. Aggregation rules:
77
- *
78
- * agents: union of all agent arrays for this id (claude before codex).
79
- * status: installed ⇔ every agent's record is installed
80
- * not-installed ⇔ every agent's record is not-installed
81
- * otherwise → update-available (partial install or any agent
82
- * with a pending update). One Apply covers all
83
- * gaps because the handler iterates `agents`.
84
- *
85
- * Non-status fields (description, currentVersion, expectedVersion,
86
- * versionSource) come from the first record we see. Today both sides report
87
- * the same description (catalog-driven) and the same versions for any
88
- * registry-pinned plugin, so this is safe; if a future divergence appears
89
- * we'll need a deliberate merge policy.
90
- */
91
74
  export declare function mergePluginsById(records: PluginState[]): PluginState[];
92
75
  /** Default: run `claude plugins list --json` (no scope flag — the CLI
93
76
  * doesn't expose one) plus the `--available` variant, then filter the
package/dist/state.js CHANGED
@@ -94,31 +94,27 @@ function dirExists(p) {
94
94
  return false;
95
95
  }
96
96
  }
97
- /**
98
- * Dedupe plugins by `id`, merging dual-Agent records into a single
99
- * multi-agent row. Aggregation rules:
100
- *
101
- * agents: union of all agent arrays for this id (claude before codex).
102
- * status: installed ⇔ every agent's record is installed
103
- * not-installed ⇔ every agent's record is not-installed
104
- * otherwise → update-available (partial install or any agent
105
- * with a pending update). One Apply covers all
106
- * gaps because the handler iterates `agents`.
107
- *
108
- * Non-status fields (description, currentVersion, expectedVersion,
109
- * versionSource) come from the first record we see. Today both sides report
110
- * the same description (catalog-driven) and the same versions for any
111
- * registry-pinned plugin, so this is safe; if a future divergence appears
112
- * we'll need a deliberate merge policy.
113
- */
114
97
  export function mergePluginsById(records) {
115
98
  const byId = new Map();
116
- const statusByIdPerAgent = new Map();
99
+ // Per-agent (agent, status, version) tuples preserved across the fold so
100
+ // the aggregation step can emit `partial-install` + `missingAgents` when
101
+ // one side is installed and the other isn't, AND pick the *stale* side's
102
+ // currentVersion when status === "update-available" (otherwise the merge
103
+ // inherited Claude's version, producing the misleading `vX → vX` display
104
+ // in the v1.18.4 verification).
105
+ const perAgentByIdEntries = new Map();
117
106
  for (const rec of records) {
107
+ // Pre-merge records are per-agent: their `agents[]` array contains the
108
+ // single Agent this row was scanned for. The merge step below unions
109
+ // those into the final dual-Agent record.
110
+ const recAgent = rec.agents[0];
111
+ const perAgentEntry = recAgent
112
+ ? { agent: recAgent, status: rec.status, currentVersion: rec.currentVersion }
113
+ : null;
118
114
  const existing = byId.get(rec.id);
119
115
  if (!existing) {
120
116
  byId.set(rec.id, { ...rec });
121
- statusByIdPerAgent.set(rec.id, [rec.status]);
117
+ perAgentByIdEntries.set(rec.id, perAgentEntry ? [perAgentEntry] : []);
122
118
  continue;
123
119
  }
124
120
  // Union agents preserving order: existing first, then any new ones.
@@ -129,28 +125,58 @@ export function mergePluginsById(records) {
129
125
  seen.add(a);
130
126
  }
131
127
  }
132
- statusByIdPerAgent.get(rec.id).push(rec.status);
128
+ if (perAgentEntry) {
129
+ perAgentByIdEntries.get(rec.id).push(perAgentEntry);
130
+ }
133
131
  }
134
- // Fold each id's per-agent status list into the aggregated status.
135
- for (const [id, statuses] of statusByIdPerAgent) {
132
+ // Fold per-agent tuples into the aggregated status, missingAgents, and
133
+ // (when applicable) the corrected currentVersion.
134
+ for (const [id, perAgent] of perAgentByIdEntries) {
136
135
  const rec = byId.get(id);
137
136
  if (!rec)
138
137
  continue;
139
- rec.status = aggregateStatus(statuses);
138
+ const aggregated = aggregateStatus(perAgent);
139
+ rec.status = aggregated.status;
140
+ if (aggregated.missingAgents && aggregated.missingAgents.length > 0) {
141
+ rec.missingAgents = aggregated.missingAgents;
142
+ }
143
+ else {
144
+ delete rec.missingAgents;
145
+ }
146
+ // When status is update-available, surface the version of the *stale*
147
+ // agent (one whose own status was update-available). Otherwise we'd
148
+ // keep whichever agent's version was merged first — Claude's, which
149
+ // may already be at the expected version, producing a `vX → vX`
150
+ // pseudo-upgrade display.
151
+ if (rec.status === "update-available") {
152
+ const stale = perAgent.find((r) => r.status === "update-available");
153
+ if (stale?.currentVersion) {
154
+ rec.currentVersion = stale.currentVersion;
155
+ }
156
+ }
140
157
  }
141
158
  return Array.from(byId.values());
142
159
  }
143
- function aggregateStatus(statuses) {
144
- if (statuses.length === 0)
145
- return "not-installed";
146
- if (statuses.every((s) => s === "installed"))
147
- return "installed";
148
- if (statuses.every((s) => s === "not-installed"))
149
- return "not-installed";
150
- // Anything else partial install, pending updates on any agent, mixed
151
- // — falls through to update-available so a single Apply backfills the
152
- // missing pieces.
153
- return "update-available";
160
+ function aggregateStatus(records) {
161
+ if (records.length === 0)
162
+ return { status: "not-installed" };
163
+ if (records.every((r) => r.status === "installed"))
164
+ return { status: "installed" };
165
+ if (records.every((r) => r.status === "not-installed"))
166
+ return { status: "not-installed" };
167
+ // Mixed. If ANY agent reports not-installed, the row is partially installed
168
+ // — the user-facing action is "install on the missing side". Surfaces as a
169
+ // distinct status from version-drift `update-available` so the UI doesn't
170
+ // render misleading `vX → vX` upgrades (the v1.18.4 deep-review symptom).
171
+ const missingAgents = records
172
+ .filter((r) => r.status === "not-installed")
173
+ .map((r) => r.agent);
174
+ if (missingAgents.length > 0) {
175
+ return { status: "partial-install", missingAgents };
176
+ }
177
+ // Otherwise version drift on at least one targeted agent — single Apply
178
+ // upgrades the stale side(s).
179
+ return { status: "update-available" };
154
180
  }
155
181
  // ---------------------------------------------------------------------------
156
182
  // Workflow
@@ -160,11 +186,12 @@ function workflowPathsForScope(scope, projectRoot, home) {
160
186
  if (scope === "user") {
161
187
  return [path.join(home, ".claude", "CLAUDE.md")];
162
188
  }
163
- // Project: <proj>/CLAUDE.md preferred, .claude/CLAUDE.md as fallback.
164
- return [
165
- path.join(projectRoot, "CLAUDE.md"),
166
- path.join(projectRoot, ".claude", "CLAUDE.md"),
167
- ];
189
+ // Project: only `<proj>/CLAUDE.md` the auriga workflow installer
190
+ // (src/workflow.ts) writes here and never to `<proj>/.claude/CLAUDE.md`.
191
+ // The old fallback collapsed onto `$HOME/.claude/CLAUDE.md` when
192
+ // projectRoot === $HOME (user runs `web-ui` from home dir), leaking
193
+ // user-scope content into the project-scope row.
194
+ return [path.join(projectRoot, "CLAUDE.md")];
168
195
  }
169
196
  function scanWorkflow(scope, projectRoot, home, catalog, warnings) {
170
197
  const expectedVersion = catalog.workflowVersion;
@@ -197,14 +224,18 @@ function scanWorkflow(scope, projectRoot, home, catalog, warnings) {
197
224
  if (line.trim().length > 0)
198
225
  break;
199
226
  }
200
- // CLAUDE.md exists but no recognizable auriga marker. Per spec degraded
201
- // path: status remains "installed" (don't clobber user content on apply)
202
- // and emit a workflow-unknown-version warning.
227
+ // CLAUDE.md exists but no recognizable auriga marker. The file is foreign
228
+ // not our workflow. Report `not-installed` honestly; the install path
229
+ // (src/workflow.ts) already protects user content by backing it up to
230
+ // `CLAUDE.md.bak` before overwriting. Conflating "something exists here"
231
+ // with "auriga workflow installed" caused the v1.18.4 verification bug
232
+ // where running web-ui from `~` reported the user's `# Global`-headed
233
+ // `~/.claude/CLAUDE.md` as an installed workflow.
203
234
  warnings.push({
204
- code: "workflow-unknown-version",
205
- message: `CLAUDE.md present but no auriga-workflow header found; cannot determine installed version.`,
235
+ code: "workflow-foreign-claudemd",
236
+ message: `Foreign CLAUDE.md detected at the workflow path — no auriga-workflow header. Install will back up to CLAUDE.md.bak.`,
206
237
  });
207
- return { status: "installed", expectedVersion, observedScope: scope };
238
+ return { status: "not-installed", expectedVersion, observedScope: scope };
208
239
  }
209
240
  // ---------------------------------------------------------------------------
210
241
  // Skills + recommendedSkills
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "auriga-cli",
3
- "version": "1.18.4",
3
+ "version": "1.18.5",
4
4
  "description": "Interactive CLI to install Claude Code harness modules (Workflow, Skills, Recommended Skills, Plugins, Hooks)",
5
5
  "license": "MIT",
6
6
  "repository": {