@ai-plugin-marketplace/core 0.2.0 → 0.4.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.
@@ -9,7 +9,7 @@
9
9
  *
10
10
  * @see docs/specs/architecture.md §8.1
11
11
  */
12
- import type { BuildOptions, BuildResult, InitOptions, MigrateOptions, MigrateResult, ScaffoldOptions, SupportReport, TargetId, ValidateOptions, ValidationResult } from './types.js';
12
+ import type { BuildOptions, BuildResult, InitOptions, MigrateOptions, MigrateResult, RefreshOptions, RefreshOutcome, ScaffoldOptions, SupportReport, TargetId, ValidateOptions, ValidationResult } from './types.js';
13
13
  /**
14
14
  * Build a single plugin or every plugin under a repo root. `path` may be a plugin directory
15
15
  * (contains `aipm.config.ts`) or a repo root (contains `plugins/`); the orchestrator detects
@@ -34,6 +34,16 @@ export declare function validate(targetPath: string, opts?: ValidateOptions): Pr
34
34
  * @public
35
35
  */
36
36
  export declare function init(targetDir: string, opts?: InitOptions): Promise<void>;
37
+ /**
38
+ * Refresh the toolkit-owned scaffold files (CI workflow, `.gitignore`) of an existing marketplace
39
+ * repo at `targetDir` to match the installed tooling — the upgrade path after
40
+ * `pnpm up @ai-plugin-marketplace/*`. Guarded by the `.aipm/scaffold.json` content-hash sidecar:
41
+ * user-modified files are reported as conflicts and left untouched unless `opts.force` is set.
42
+ * Returns one outcome per managed file; never rejects on conflict.
43
+ *
44
+ * @public
45
+ */
46
+ export declare function refreshScaffold(targetDir: string, opts?: RefreshOptions): Promise<RefreshOutcome[]>;
37
47
  /**
38
48
  * Scaffold a new plugin under the cwd's configured plugins root (`<cwd>/plugins/<name>` by
39
49
  * default, or the relocated `pluginsRoot` from an `aipm.repo.ts`). The plugins directory is
@@ -1 +1 @@
1
- {"version":3,"file":"operations.d.ts","sourceRoot":"","sources":["../../src/pipeline/operations.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAUH,OAAO,KAAK,EACV,YAAY,EACZ,WAAW,EACX,WAAW,EACX,cAAc,EACd,aAAa,EACb,eAAe,EACf,aAAa,EACb,QAAQ,EACR,eAAe,EACf,gBAAgB,EACjB,MAAM,YAAY,CAAC;AAOpB;;;;;;GAMG;AACH,wBAAgB,KAAK,CAAC,UAAU,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,YAAY,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC,CAErF;AAED;;;;;GAKG;AACH,wBAAgB,QAAQ,CAAC,UAAU,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,eAAe,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAE9F;AAED;;;;;;;GAOG;AACH,wBAAgB,IAAI,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAEzE;AAED;;;;;;;GAOG;AACH,wBAAsB,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,GAAE,eAAoB,GAAG,OAAO,CAAC,IAAI,CAAC,CAKtF;AAED;;;;;;GAMG;AACH,wBAAgB,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,cAAc,GAAG,OAAO,CAAC,aAAa,CAAC,CAMrF;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC,CAEtE;AAED;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,CAE5E;AAED;;;;GAIG;AACH,wBAAgB,WAAW,IAAI,SAAS,QAAQ,EAAE,CAEjD"}
1
+ {"version":3,"file":"operations.d.ts","sourceRoot":"","sources":["../../src/pipeline/operations.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAWH,OAAO,KAAK,EACV,YAAY,EACZ,WAAW,EACX,WAAW,EACX,cAAc,EACd,aAAa,EACb,cAAc,EACd,cAAc,EACd,eAAe,EACf,aAAa,EACb,QAAQ,EACR,eAAe,EACf,gBAAgB,EACjB,MAAM,YAAY,CAAC;AAOpB;;;;;;GAMG;AACH,wBAAgB,KAAK,CAAC,UAAU,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,YAAY,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC,CAErF;AAED;;;;;GAKG;AACH,wBAAgB,QAAQ,CAAC,UAAU,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,eAAe,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAE9F;AAED;;;;;;;GAOG;AACH,wBAAgB,IAAI,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAEzE;AAED;;;;;;;;GAQG;AACH,wBAAgB,eAAe,CAC7B,SAAS,EAAE,MAAM,EACjB,IAAI,CAAC,EAAE,cAAc,GACpB,OAAO,CAAC,cAAc,EAAE,CAAC,CAK3B;AAED;;;;;;;GAOG;AACH,wBAAsB,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,GAAE,eAAoB,GAAG,OAAO,CAAC,IAAI,CAAC,CAKtF;AAED;;;;;;GAMG;AACH,wBAAgB,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,cAAc,GAAG,OAAO,CAAC,aAAa,CAAC,CAMrF;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC,CAEtE;AAED;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,CAE5E;AAED;;;;GAIG;AACH,wBAAgB,WAAW,IAAI,SAAS,QAAQ,EAAE,CAEjD"}
@@ -13,6 +13,7 @@ import * as path from 'node:path';
13
13
  import { runBuild } from './build.js';
14
14
  import { runInit } from './init.js';
15
15
  import { loadRepoConfig } from './load-config.js';
16
+ import { runRefreshScaffold } from './scaffold-refresh.js';
16
17
  import { runScaffold, runAddTarget, runCheckSupport } from './scaffold.js';
17
18
  import { TARGET_IDS } from './types.js';
18
19
  import { runValidate } from './validate.js';
@@ -50,6 +51,21 @@ export function validate(targetPath, opts) {
50
51
  export function init(targetDir, opts) {
51
52
  return runInit(targetDir, opts);
52
53
  }
54
+ /**
55
+ * Refresh the toolkit-owned scaffold files (CI workflow, `.gitignore`) of an existing marketplace
56
+ * repo at `targetDir` to match the installed tooling — the upgrade path after
57
+ * `pnpm up @ai-plugin-marketplace/*`. Guarded by the `.aipm/scaffold.json` content-hash sidecar:
58
+ * user-modified files are reported as conflicts and left untouched unless `opts.force` is set.
59
+ * Returns one outcome per managed file; never rejects on conflict.
60
+ *
61
+ * @public
62
+ */
63
+ export function refreshScaffold(targetDir, opts) {
64
+ // Defer into the microtask queue so a synchronous failure in the orchestrator (e.g. an I/O error)
65
+ // surfaces as a rejected promise — matching `refreshScaffold(...).catch(...)` expectations —
66
+ // rather than throwing from this call before the promise exists.
67
+ return Promise.resolve().then(() => runRefreshScaffold(targetDir, opts));
68
+ }
53
69
  /**
54
70
  * Scaffold a new plugin under the cwd's configured plugins root (`<cwd>/plugins/<name>` by
55
71
  * default, or the relocated `pluginsRoot` from an `aipm.repo.ts`). The plugins directory is
@@ -1 +1 @@
1
- {"version":3,"file":"operations.js","sourceRoot":"","sources":["../../src/pipeline/operations.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAElC,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACtC,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAClD,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAC3E,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AACxC,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAc5C,sGAAsG;AACtG,SAAS,IAAI;IACX,OAAO,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC;AACpC,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,KAAK,CAAC,UAAkB,EAAE,IAAmB;IAC3D,OAAO,QAAQ,CAAC,UAAU,EAAE,IAAI,CAAC,CAAC;AACpC,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,QAAQ,CAAC,UAAkB,EAAE,IAAsB;IACjE,OAAO,WAAW,CAAC,UAAU,EAAE,EAAE,GAAG,IAAI,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,CAAC,CAAC;AAC1D,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,IAAI,CAAC,SAAiB,EAAE,IAAkB;IACxD,OAAO,OAAO,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC;AAClC,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,QAAQ,CAAC,IAAY,EAAE,OAAwB,EAAE;IACrE,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC;IAC1B,MAAM,UAAU,GAAG,MAAM,cAAc,CAAC,GAAG,CAAC,CAAC;IAC7C,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,UAAU,CAAC,WAAW,CAAC,CAAC;IAC1D,OAAO,WAAW,CAAC,IAAI,EAAE,UAAU,EAAE,IAAI,CAAC,CAAC;AAC7C,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,OAAO,CAAC,KAAa,EAAE,KAAsB;IAC3D,OAAO,OAAO,CAAC,OAAO,CAAC;QACrB,MAAM,EAAE,sBAAsB;QAC9B,iBAAiB,EAAE,CAAC;QACpB,YAAY,EAAE,EAAE;KACjB,CAAC,CAAC;AACL,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,YAAY,CAAC,SAAiB;IAC5C,OAAO,eAAe,CAAC,SAAS,CAAC,CAAC;AACpC,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,SAAS,CAAC,SAAiB,EAAE,MAAgB;IAC3D,OAAO,YAAY,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC;AACzC,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,WAAW;IACzB,OAAO,UAAU,CAAC;AACpB,CAAC"}
1
+ {"version":3,"file":"operations.js","sourceRoot":"","sources":["../../src/pipeline/operations.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAElC,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACtC,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAClD,OAAO,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAC3D,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAC3E,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AACxC,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAgB5C,sGAAsG;AACtG,SAAS,IAAI;IACX,OAAO,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC;AACpC,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,KAAK,CAAC,UAAkB,EAAE,IAAmB;IAC3D,OAAO,QAAQ,CAAC,UAAU,EAAE,IAAI,CAAC,CAAC;AACpC,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,QAAQ,CAAC,UAAkB,EAAE,IAAsB;IACjE,OAAO,WAAW,CAAC,UAAU,EAAE,EAAE,GAAG,IAAI,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,CAAC,CAAC;AAC1D,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,IAAI,CAAC,SAAiB,EAAE,IAAkB;IACxD,OAAO,OAAO,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC;AAClC,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,eAAe,CAC7B,SAAiB,EACjB,IAAqB;IAErB,kGAAkG;IAClG,6FAA6F;IAC7F,iEAAiE;IACjE,OAAO,OAAO,CAAC,OAAO,EAAE,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,kBAAkB,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC,CAAC;AAC3E,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,QAAQ,CAAC,IAAY,EAAE,OAAwB,EAAE;IACrE,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC;IAC1B,MAAM,UAAU,GAAG,MAAM,cAAc,CAAC,GAAG,CAAC,CAAC;IAC7C,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,UAAU,CAAC,WAAW,CAAC,CAAC;IAC1D,OAAO,WAAW,CAAC,IAAI,EAAE,UAAU,EAAE,IAAI,CAAC,CAAC;AAC7C,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,OAAO,CAAC,KAAa,EAAE,KAAsB;IAC3D,OAAO,OAAO,CAAC,OAAO,CAAC;QACrB,MAAM,EAAE,sBAAsB;QAC9B,iBAAiB,EAAE,CAAC;QACpB,YAAY,EAAE,EAAE;KACjB,CAAC,CAAC;AACL,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,YAAY,CAAC,SAAiB;IAC5C,OAAO,eAAe,CAAC,SAAS,CAAC,CAAC;AACpC,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,SAAS,CAAC,SAAiB,EAAE,MAAgB;IAC3D,OAAO,YAAY,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC;AACzC,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,WAAW;IACzB,OAAO,UAAU,CAAC;AACpB,CAAC"}
@@ -0,0 +1,80 @@
1
+ /**
2
+ * `aipm init --refresh` — keep a marketplace repo's **toolkit-owned scaffold files** (the CI
3
+ * workflow and `.gitignore`) in sync with the installed tooling, and serve as the upgrade path for
4
+ * any marketplace repo after `pnpm up @ai-plugin-marketplace/*`.
5
+ *
6
+ * Safety rests on a content-hash sidecar at `.aipm/scaffold.json` (mirroring the root-emission
7
+ * sidecar `.aipm/generated-root.json`): it records a hash of the exact content the toolkit last
8
+ * wrote each managed file. Refresh updates a file only when it is still pristine (matches the
9
+ * recorded hash) or missing; a file the user has edited surfaces as a reported conflict and is left
10
+ * untouched unless `--force` is given. A repo with no sidecar entry bootstraps: an already-in-sync
11
+ * file is adopted silently, a diverged one is a conflict to review.
12
+ *
13
+ * The managed set is deliberately narrow — only the pure tooling-recipe files (see
14
+ * {@link buildManagedScaffoldFiles}). `package.json` (deps are `pnpm up`'s job), `aipm.workspace.ts`
15
+ * (repo identity), `README.md` (authored), plugins, and `aipm build` output are never touched.
16
+ *
17
+ * @see docs/specs/scaffold-refresh-and-upgrade.md
18
+ */
19
+ import type { RefreshOptions, RefreshOutcome } from './types.js';
20
+ /** A recorded `{ path, hash }` pair: the hash of the content the toolkit last wrote at `path`. */
21
+ interface SidecarEntry {
22
+ path: string;
23
+ hash: string;
24
+ }
25
+ /** Content hash recorded in the sidecar. `sha256-`-prefixed hex over the UTF-8 bytes. */
26
+ export declare function hashScaffoldContent(content: string): string;
27
+ /**
28
+ * Serialize a sidecar payload: `{ version, files }` with `files` sorted by path for determinism,
29
+ * 2-space JSON + trailing newline (matching the repo's other generated JSON).
30
+ */
31
+ export declare function serializeScaffoldSidecar(entries: readonly SidecarEntry[]): string;
32
+ /** Inputs to the pure per-file refresh decision. */
33
+ export interface ManagedFileDecisionInput {
34
+ /** Canonical content the toolkit would render for this file. */
35
+ render: string;
36
+ /** Current on-disk content, or `null` when the file is missing. */
37
+ current: string | null;
38
+ /** Hash recorded in the sidecar for this file, or `null` when untracked. */
39
+ recordedHash: string | null;
40
+ /** Whether `--force` was given. */
41
+ force: boolean;
42
+ }
43
+ /** Result of the pure per-file refresh decision. */
44
+ export interface ManagedFileDecision {
45
+ status: RefreshOutcome['status'];
46
+ /** Whether to write `render` to disk. */
47
+ write: boolean;
48
+ /** Hash to record in the new sidecar, or `null` to preserve the prior entry (or stay untracked). */
49
+ recordHash: string | null;
50
+ }
51
+ /**
52
+ * Decide what to do with one managed scaffold file. Pure — all filesystem state is passed in — so
53
+ * the decision table is unit-testable in isolation.
54
+ *
55
+ * | On-disk state | Result |
56
+ * |-------------------------------------------------|---------------|
57
+ * | missing | `recreated` |
58
+ * | matches the current render | `unchanged` |
59
+ * | differs from render, matches recorded hash | `updated` |
60
+ * | differs from render & recorded (or untracked) | `conflict` * |
61
+ * | …same, with `force` | `overwritten` |
62
+ *
63
+ * \* `unchanged` also adopts an untracked-but-in-sync file by recording its hash.
64
+ */
65
+ export declare function decideManagedFile(input: ManagedFileDecisionInput): ManagedFileDecision;
66
+ /**
67
+ * Seed `.aipm/scaffold.json` for a freshly scaffolded repo, recording the hash of every managed
68
+ * scaffold file as just written by `aipm init`. Called by {@link runInit} after the seed tree is
69
+ * written so the first `--refresh` has a baseline.
70
+ */
71
+ export declare function writeScaffoldSidecar(repoRoot: string): void;
72
+ /**
73
+ * Refresh the toolkit-owned scaffold files of an existing repo at `repoRoot`, re-rendering each
74
+ * from the installed tooling and updating it in place when safe (see {@link decideManagedFile}).
75
+ * Writes the updated `.aipm/scaffold.json` sidecar and returns one {@link RefreshOutcome} per
76
+ * managed file. Never throws on conflict — conflicts are reported, not failures.
77
+ */
78
+ export declare function runRefreshScaffold(repoRoot: string, opts?: RefreshOptions): RefreshOutcome[];
79
+ export {};
80
+ //# sourceMappingURL=scaffold-refresh.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scaffold-refresh.d.ts","sourceRoot":"","sources":["../../src/pipeline/scaffold-refresh.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAOH,OAAO,KAAK,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAOjE,kGAAkG;AAClG,UAAU,YAAY;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd;AAOD,yFAAyF;AACzF,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAE3D;AAED;;;GAGG;AACH,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,SAAS,YAAY,EAAE,GAAG,MAAM,CAGjF;AA+BD,oDAAoD;AACpD,MAAM,WAAW,wBAAwB;IACvC,gEAAgE;IAChE,MAAM,EAAE,MAAM,CAAC;IACf,mEAAmE;IACnE,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,4EAA4E;IAC5E,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,mCAAmC;IACnC,KAAK,EAAE,OAAO,CAAC;CAChB;AAED,oDAAoD;AACpD,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,cAAc,CAAC,QAAQ,CAAC,CAAC;IACjC,yCAAyC;IACzC,KAAK,EAAE,OAAO,CAAC;IACf,oGAAoG;IACpG,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,wBAAwB,GAAG,mBAAmB,CAmBtF;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAQ3D;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,GAAE,cAAmB,GAAG,cAAc,EAAE,CAmChG"}
@@ -0,0 +1,152 @@
1
+ /**
2
+ * `aipm init --refresh` — keep a marketplace repo's **toolkit-owned scaffold files** (the CI
3
+ * workflow and `.gitignore`) in sync with the installed tooling, and serve as the upgrade path for
4
+ * any marketplace repo after `pnpm up @ai-plugin-marketplace/*`.
5
+ *
6
+ * Safety rests on a content-hash sidecar at `.aipm/scaffold.json` (mirroring the root-emission
7
+ * sidecar `.aipm/generated-root.json`): it records a hash of the exact content the toolkit last
8
+ * wrote each managed file. Refresh updates a file only when it is still pristine (matches the
9
+ * recorded hash) or missing; a file the user has edited surfaces as a reported conflict and is left
10
+ * untouched unless `--force` is given. A repo with no sidecar entry bootstraps: an already-in-sync
11
+ * file is adopted silently, a diverged one is a conflict to review.
12
+ *
13
+ * The managed set is deliberately narrow — only the pure tooling-recipe files (see
14
+ * {@link buildManagedScaffoldFiles}). `package.json` (deps are `pnpm up`'s job), `aipm.workspace.ts`
15
+ * (repo identity), `README.md` (authored), plugins, and `aipm build` output are never touched.
16
+ *
17
+ * @see docs/specs/scaffold-refresh-and-upgrade.md
18
+ */
19
+ import { createHash } from 'node:crypto';
20
+ import * as fs from 'node:fs';
21
+ import * as path from 'node:path';
22
+ import { buildManagedScaffoldFiles } from './init-template.js';
23
+ /** Sidecar location relative to the repo root. */
24
+ const SIDECAR_REL = ['.aipm', 'scaffold.json'];
25
+ /** Schema version of the sidecar payload. */
26
+ const SIDECAR_VERSION = 1;
27
+ /** Absolute path of the scaffold sidecar under `repoRoot`. */
28
+ function sidecarPath(repoRoot) {
29
+ return path.join(repoRoot, ...SIDECAR_REL);
30
+ }
31
+ /** Content hash recorded in the sidecar. `sha256-`-prefixed hex over the UTF-8 bytes. */
32
+ export function hashScaffoldContent(content) {
33
+ return `sha256-${createHash('sha256').update(content, 'utf-8').digest('hex')}`;
34
+ }
35
+ /**
36
+ * Serialize a sidecar payload: `{ version, files }` with `files` sorted by path for determinism,
37
+ * 2-space JSON + trailing newline (matching the repo's other generated JSON).
38
+ */
39
+ export function serializeScaffoldSidecar(entries) {
40
+ const files = [...entries].sort((a, b) => a.path.localeCompare(b.path));
41
+ return `${JSON.stringify({ version: SIDECAR_VERSION, files }, null, 2)}\n`;
42
+ }
43
+ /** Read the prior sidecar into a `path → hash` map. Missing or malformed sidecar → empty map. */
44
+ function readSidecar(repoRoot) {
45
+ const map = new Map();
46
+ const abs = sidecarPath(repoRoot);
47
+ if (!fs.existsSync(abs))
48
+ return map;
49
+ // Read errors (permissions, transient I/O) propagate — they are real operational problems, not a
50
+ // "malformed sidecar" we should silently swallow. Only a JSON parse failure is tolerated.
51
+ const raw = fs.readFileSync(abs, 'utf-8');
52
+ let parsed;
53
+ try {
54
+ parsed = JSON.parse(raw);
55
+ }
56
+ catch {
57
+ // A malformed sidecar is treated as absent: every managed file becomes "untracked" and must
58
+ // either already match the current render (adopted) or be resolved via --force.
59
+ return map;
60
+ }
61
+ if (Array.isArray(parsed.files)) {
62
+ for (const rawEntry of parsed.files) {
63
+ const entry = rawEntry;
64
+ if (typeof entry.path === 'string' && typeof entry.hash === 'string') {
65
+ map.set(entry.path, entry.hash);
66
+ }
67
+ }
68
+ }
69
+ return map;
70
+ }
71
+ /**
72
+ * Decide what to do with one managed scaffold file. Pure — all filesystem state is passed in — so
73
+ * the decision table is unit-testable in isolation.
74
+ *
75
+ * | On-disk state | Result |
76
+ * |-------------------------------------------------|---------------|
77
+ * | missing | `recreated` |
78
+ * | matches the current render | `unchanged` |
79
+ * | differs from render, matches recorded hash | `updated` |
80
+ * | differs from render & recorded (or untracked) | `conflict` * |
81
+ * | …same, with `force` | `overwritten` |
82
+ *
83
+ * \* `unchanged` also adopts an untracked-but-in-sync file by recording its hash.
84
+ */
85
+ export function decideManagedFile(input) {
86
+ const { render, current, recordedHash, force } = input;
87
+ const renderHash = hashScaffoldContent(render);
88
+ if (current === null)
89
+ return { status: 'recreated', write: true, recordHash: renderHash };
90
+ const currentHash = hashScaffoldContent(current);
91
+ if (currentHash === renderHash)
92
+ return { status: 'unchanged', write: false, recordHash: renderHash };
93
+ // Content differs from the current render.
94
+ if (recordedHash !== null && currentHash === recordedHash) {
95
+ // Pristine: the file is exactly what the toolkit last wrote, so it is safe to upgrade.
96
+ return { status: 'updated', write: true, recordHash: renderHash };
97
+ }
98
+ // Diverged from the recorded hash (user-edited) or untracked and not in sync.
99
+ if (force)
100
+ return { status: 'overwritten', write: true, recordHash: renderHash };
101
+ return { status: 'conflict', write: false, recordHash: null };
102
+ }
103
+ /**
104
+ * Seed `.aipm/scaffold.json` for a freshly scaffolded repo, recording the hash of every managed
105
+ * scaffold file as just written by `aipm init`. Called by {@link runInit} after the seed tree is
106
+ * written so the first `--refresh` has a baseline.
107
+ */
108
+ export function writeScaffoldSidecar(repoRoot) {
109
+ const entries = buildManagedScaffoldFiles().map((file) => ({
110
+ path: file.path,
111
+ hash: hashScaffoldContent(file.content),
112
+ }));
113
+ const abs = sidecarPath(repoRoot);
114
+ fs.mkdirSync(path.dirname(abs), { recursive: true });
115
+ fs.writeFileSync(abs, serializeScaffoldSidecar(entries), 'utf-8');
116
+ }
117
+ /**
118
+ * Refresh the toolkit-owned scaffold files of an existing repo at `repoRoot`, re-rendering each
119
+ * from the installed tooling and updating it in place when safe (see {@link decideManagedFile}).
120
+ * Writes the updated `.aipm/scaffold.json` sidecar and returns one {@link RefreshOutcome} per
121
+ * managed file. Never throws on conflict — conflicts are reported, not failures.
122
+ */
123
+ export function runRefreshScaffold(repoRoot, opts = {}) {
124
+ const force = opts.force ?? false;
125
+ const resolved = path.resolve(repoRoot);
126
+ const recorded = readSidecar(resolved);
127
+ const outcomes = [];
128
+ const newEntries = [];
129
+ for (const file of buildManagedScaffoldFiles()) {
130
+ const abs = path.join(resolved, file.path);
131
+ const current = fs.existsSync(abs) ? fs.readFileSync(abs, 'utf-8') : null;
132
+ const recordedHash = recorded.get(file.path) ?? null;
133
+ const decision = decideManagedFile({ render: file.content, current, recordedHash, force });
134
+ if (decision.write) {
135
+ fs.mkdirSync(path.dirname(abs), { recursive: true });
136
+ fs.writeFileSync(abs, file.content, 'utf-8');
137
+ }
138
+ if (decision.recordHash !== null) {
139
+ newEntries.push({ path: file.path, hash: decision.recordHash });
140
+ }
141
+ else if (recordedHash !== null) {
142
+ // Conflict left untouched: preserve the prior recorded hash so it stays tracked.
143
+ newEntries.push({ path: file.path, hash: recordedHash });
144
+ }
145
+ outcomes.push({ path: file.path, status: decision.status });
146
+ }
147
+ const sidecarAbs = sidecarPath(resolved);
148
+ fs.mkdirSync(path.dirname(sidecarAbs), { recursive: true });
149
+ fs.writeFileSync(sidecarAbs, serializeScaffoldSidecar(newEntries), 'utf-8');
150
+ return outcomes;
151
+ }
152
+ //# sourceMappingURL=scaffold-refresh.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scaffold-refresh.js","sourceRoot":"","sources":["../../src/pipeline/scaffold-refresh.ts"],"names":[],"mappings":"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"}
@@ -99,9 +99,15 @@ export interface ValidationResult {
99
99
  * host software / the author). Generation refuses to overwrite it rather than clobber repo-root
100
100
  * state.
101
101
  *
102
+ * `default-marketplace-name` — the repo's effective marketplace `name` (or `owner.name`) is still
103
+ * a template placeholder (e.g. `ai-plugin-marketplace`, `my-ai-plugins`, `Your Name`). Two
104
+ * marketplaces registered under the same name collide on install — the later one shadows/strands
105
+ * the earlier one's plugins — so a placeholder must be renamed to a unique value. Always SOFT: a
106
+ * warning, never a build failure.
107
+ *
102
108
  * @public
103
109
  */
104
- export type FindingCode = 'envelope-invalid' | 'repo-config-invalid' | 'envelope-adherence' | 'schema-invalid' | 'name-consistency' | 'mcp-key-sync' | 'marketplace-registration' | 'freshness' | 'single-artifact-host' | 'root-artifact-collision';
110
+ export type FindingCode = 'envelope-invalid' | 'repo-config-invalid' | 'envelope-adherence' | 'schema-invalid' | 'name-consistency' | 'mcp-key-sync' | 'marketplace-registration' | 'freshness' | 'single-artifact-host' | 'root-artifact-collision' | 'default-marketplace-name';
105
111
  /**
106
112
  * A single validation finding.
107
113
  *
@@ -139,6 +145,56 @@ export interface InitOptions {
139
145
  * directory.
140
146
  */
141
147
  name?: string;
148
+ /**
149
+ * Marketplace name written into the generated repo-root registries' top-level `name` (and used
150
+ * for the registries' `owner.name`). This is the identity host platforms register the
151
+ * marketplace under, and it MUST be unique across marketplaces — two marketplaces sharing a name
152
+ * collide on install, with the later one shadowing/stranding the earlier one's plugins.
153
+ *
154
+ * Distinct from {@link InitOptions.name} (the `package.json` name). When omitted, `runInit`
155
+ * resolves a default at the I/O boundary (`${USER}-ai-plugins`, falling back to `my-ai-plugins`
156
+ * when `USER` is unset). `init-template.ts` is a pure function of its inputs, so the resolved
157
+ * value is passed in rather than read from the environment there.
158
+ */
159
+ marketplaceName?: string;
160
+ /**
161
+ * Version of `@ai-plugin-marketplace/cli` to pin the generated `package.json`'s `cli`
162
+ * dev dependency to (as `^<cliVersion>`). The cli entrypoint supplies its own version here; cli
163
+ * and core ship independently and may differ (e.g. `cli 0.1.1` ships with `core 0.2.0`). When
164
+ * omitted, falls back to core's own version (the historical lockstep assumption).
165
+ */
166
+ cliVersion?: string;
167
+ }
168
+ /**
169
+ * Options for {@link refreshScaffold}.
170
+ *
171
+ * @public
172
+ */
173
+ export interface RefreshOptions {
174
+ /**
175
+ * Overwrite toolkit-owned scaffold files even when their on-disk content has diverged from what
176
+ * the toolkit last wrote (i.e. the user edited them). Without this, diverged files are reported
177
+ * as conflicts and left untouched.
178
+ */
179
+ force?: boolean;
180
+ }
181
+ /**
182
+ * Per-file outcome of a {@link refreshScaffold} run. Returned (one per managed scaffold file) so
183
+ * the CLI can report what changed without re-deriving it.
184
+ *
185
+ * @public
186
+ */
187
+ export interface RefreshOutcome {
188
+ /** Repo-relative POSIX path of the managed scaffold file. */
189
+ path: string;
190
+ /**
191
+ * - `updated` — content changed and was rewritten (file was pristine or `--force`).
192
+ * - `unchanged` — already matched the current render; nothing written.
193
+ * - `recreated` — file was missing and was written from the render.
194
+ * - `conflict` — on-disk content diverged from the recorded hash; left untouched (no `--force`).
195
+ * - `overwritten` — diverged but rewritten because `--force` was set.
196
+ */
197
+ status: 'updated' | 'unchanged' | 'recreated' | 'conflict' | 'overwritten';
142
198
  }
143
199
  /**
144
200
  * Options for {@link migrate}.
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/pipeline/types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/pipeline/types.ts"],"names":[],"mappings":"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"}
@@ -7,6 +7,7 @@
7
7
  *
8
8
  * @see docs/specs/architecture.md §6, §8.1, §10.1, §10.2
9
9
  */
10
+ import type { AipmWorkspace } from '../config.js';
10
11
  import type { Finding, TargetId, ValidateOptions, ValidationResult } from './types.js';
11
12
  /**
12
13
  * Minimum required artifacts for each target. Missing any of these when the target is
@@ -14,6 +15,17 @@ import type { Finding, TargetId, ValidateOptions, ValidationResult } from './typ
14
15
  * Vercel requires at least one skills/<name>/SKILL.md — handled specially below.
15
16
  */
16
17
  export declare const TARGET_MIN_REQUIRED: Record<TargetId, string[]>;
18
+ /**
19
+ * Repo-level check: warn (SOFT) when the effective marketplace `name` or `owner.name` is still a
20
+ * template placeholder. A placeholder name collides with the upstream `ai-plugin-marketplace`
21
+ * marketplace (or any other fork that kept the default) — when two marketplaces register under the
22
+ * same name, the later install shadows/strands the earlier's plugins. Emitting nothing when no
23
+ * marketplace metadata is declared (an empty `aipm init` repo before any name is set).
24
+ *
25
+ * Always SOFT: this is advice the author should act on, never a reason to fail the build (it does
26
+ * not flip `passed`).
27
+ */
28
+ export declare function checkDefaultMarketplaceName(repoRoot: string, workspace: AipmWorkspace | undefined): Finding[];
17
29
  /**
18
30
  * Validate an already-loaded `aipm.config.ts` value against AipmConfig's Zod schema.
19
31
  * Loading from disk is the pipeline orchestrator's job (Stage 5). Here we only validate shape.
@@ -1 +1 @@
1
- {"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../../src/pipeline/validate.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAsCH,OAAO,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAiCvF;;;;GAIG;AACH,eAAO,MAAM,mBAAmB,EAAE,MAAM,CAAC,QAAQ,EAAE,MAAM,EAAE,CAO1D,CAAC;AAoHF;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CAAC,SAAS,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,EAAE,CAsBvF;AAMD;;;;;GAKG;AACH,wBAAgB,yBAAyB,CACvC,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,SAAS,QAAQ,EAAE,GAC5B,OAAO,EAAE,CA4FX;AAMD;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CACrC,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,SAAS,QAAQ,EAAE,GAC5B,OAAO,EAAE,CAmGX;AAMD;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,SAAS,QAAQ,EAAE,GAAG,OAAO,EAAE,CAgD9F;AAoDD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,+BAA+B,CAC7C,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,SAAS,QAAQ,EAAE,GAC5B,OAAO,EAAE,CA0FX;AAMD;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CACjC,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,SAAS,QAAQ,EAAE,GAC5B,OAAO,EAAE,CAOX;AAyZD;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAsB,WAAW,CAC/B,UAAU,EAAE,MAAM,EAClB,IAAI,CAAC,EAAE,eAAe,GAAG;IAAE,EAAE,CAAC,EAAE,OAAO,CAAA;CAAE,GACxC,OAAO,CAAC,gBAAgB,CAAC,CAoI3B"}
1
+ {"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../../src/pipeline/validate.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAqCH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAClD,OAAO,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAiCvF;;;;GAIG;AACH,eAAO,MAAM,mBAAmB,EAAE,MAAM,CAAC,QAAQ,EAAE,MAAM,EAAE,CAO1D,CAAC;AA8LF;;;;;;;;;GASG;AACH,wBAAgB,2BAA2B,CACzC,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,aAAa,GAAG,SAAS,GACnC,OAAO,EAAE,CAuBX;AAMD;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CAAC,SAAS,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,EAAE,CAsBvF;AAMD;;;;;GAKG;AACH,wBAAgB,yBAAyB,CACvC,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,SAAS,QAAQ,EAAE,GAC5B,OAAO,EAAE,CA4FX;AAMD;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CACrC,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,SAAS,QAAQ,EAAE,GAC5B,OAAO,EAAE,CAmGX;AAMD;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,SAAS,QAAQ,EAAE,GAAG,OAAO,EAAE,CAgD9F;AAoDD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,+BAA+B,CAC7C,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,SAAS,QAAQ,EAAE,GAC5B,OAAO,EAAE,CA0FX;AAMD;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CACjC,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,SAAS,QAAQ,EAAE,GAC5B,OAAO,EAAE,CAOX;AAyZD;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAsB,WAAW,CAC/B,UAAU,EAAE,MAAM,EAClB,IAAI,CAAC,EAAE,eAAe,GAAG;IAAE,EAAE,CAAC,EAAE,OAAO,CAAA;CAAE,GACxC,OAAO,CAAC,gBAAgB,CAAC,CA+I3B"}
@@ -138,6 +138,98 @@ function hard(code, plugin, message, hint) {
138
138
  return { severity: 'hard', code, plugin, message, ...(hint !== undefined ? { hint } : {}) };
139
139
  }
140
140
  // ---------------------------------------------------------------------------
141
+ // Default / placeholder marketplace name check
142
+ // ---------------------------------------------------------------------------
143
+ /**
144
+ * Marketplace `name` values that are template placeholders, not a real identity. `ai-plugin-marketplace`
145
+ * is the upstream template repo's own name (a fork that never renamed collides with upstream);
146
+ * `my-ai-plugins` is the placeholder the template ships and the fallback `aipm init` writes when
147
+ * `$USER` is unset (intentionally flagged so the author is nudged to pass `--name`).
148
+ */
149
+ const PLACEHOLDER_MARKETPLACE_NAMES = new Set([
150
+ 'ai-plugin-marketplace',
151
+ 'my-ai-plugins',
152
+ ]);
153
+ /**
154
+ * Marketplace `owner.name` values that are template placeholders rather than a real owner.
155
+ */
156
+ const PLACEHOLDER_OWNER_NAMES = new Set([
157
+ 'AI Plugin Marketplace Template',
158
+ 'Your Name',
159
+ ]);
160
+ /** Schema for the marketplace metadata this check reads from a generated registry JSON. */
161
+ const registryMetadataSchema = z
162
+ .object({
163
+ name: z.string().optional(),
164
+ owner: z.object({ name: z.string().optional() }).loose().optional(),
165
+ })
166
+ .loose();
167
+ /**
168
+ * Resolve the repo's effective marketplace identity for the default-name check. Prefers the
169
+ * authored `aipm.workspace.ts` (which `runValidate` already loaded) and falls back to reading a
170
+ * committed repo-root registry's top-level `name`/`owner.name` (the hand-authored / `aipm init`
171
+ * path, where no workspace exists). The first managed registry that parses and declares a `name`
172
+ * OR an `owner.name` wins; all generated registries carry the same marketplace identity, so any
173
+ * one is representative.
174
+ */
175
+ function resolveMarketplaceIdentity(repoRoot, workspace) {
176
+ if (workspace !== undefined) {
177
+ const { marketplace } = workspace;
178
+ return {
179
+ name: marketplace.name,
180
+ ...(marketplace.owner !== undefined ? { ownerName: marketplace.owner.name } : {}),
181
+ };
182
+ }
183
+ for (const registryPath of managedRegistryPaths(repoRoot)) {
184
+ if (!fs.existsSync(registryPath))
185
+ continue;
186
+ const parsed = registryMetadataSchema.safeParse(tryReadJson(registryPath));
187
+ if (!parsed.success)
188
+ continue;
189
+ const { name, owner } = parsed.data;
190
+ // Only treat this registry as the identity source once it declares a `name` or an `owner.name`;
191
+ // an empty `{ "plugins": [] }` registry carries no identity, so keep scanning the others.
192
+ if (name === undefined && owner?.name === undefined)
193
+ continue;
194
+ return {
195
+ ...(name !== undefined ? { name } : {}),
196
+ ...(owner?.name !== undefined ? { ownerName: owner.name } : {}),
197
+ };
198
+ }
199
+ return {};
200
+ }
201
+ /**
202
+ * Repo-level check: warn (SOFT) when the effective marketplace `name` or `owner.name` is still a
203
+ * template placeholder. A placeholder name collides with the upstream `ai-plugin-marketplace`
204
+ * marketplace (or any other fork that kept the default) — when two marketplaces register under the
205
+ * same name, the later install shadows/strands the earlier's plugins. Emitting nothing when no
206
+ * marketplace metadata is declared (an empty `aipm init` repo before any name is set).
207
+ *
208
+ * Always SOFT: this is advice the author should act on, never a reason to fail the build (it does
209
+ * not flip `passed`).
210
+ */
211
+ export function checkDefaultMarketplaceName(repoRoot, workspace) {
212
+ const { name, ownerName } = resolveMarketplaceIdentity(repoRoot, workspace);
213
+ const findings = [];
214
+ if (name !== undefined && PLACEHOLDER_MARKETPLACE_NAMES.has(name)) {
215
+ findings.push({
216
+ severity: 'soft',
217
+ code: 'default-marketplace-name',
218
+ message: `Marketplace name '${name}' is a template default. Two marketplaces registered under the same name collide on install — the later one shadows/strands the earlier one's plugins (including the upstream 'ai-plugin-marketplace').`,
219
+ hint: "Rename it to a unique value (convention: '<your-handle>-ai-plugins'): set marketplace.name in aipm.workspace.ts, or the top-level `name` in the repo-root registries (.claude-plugin/.cursor-plugin/marketplace.json) when there is no workspace. New repos can pass `aipm init --name <name>`.",
220
+ });
221
+ }
222
+ if (ownerName !== undefined && PLACEHOLDER_OWNER_NAMES.has(ownerName)) {
223
+ findings.push({
224
+ severity: 'soft',
225
+ code: 'default-marketplace-name',
226
+ message: `Marketplace owner name '${ownerName}' is a template default, not a real owner.`,
227
+ hint: 'Set marketplace.owner.name in aipm.workspace.ts (or the repo-root registries) to your name or organization.',
228
+ });
229
+ }
230
+ return findings;
231
+ }
232
+ // ---------------------------------------------------------------------------
141
233
  // validateEnvelopeShape
142
234
  // ---------------------------------------------------------------------------
143
235
  /**
@@ -894,7 +986,17 @@ export async function runValidate(targetPath, opts) {
894
986
  findings.push(...checkFreshness(pluginDir, distDir, envelope, ci));
895
987
  }
896
988
  }
897
- // ── 6. Repo-level generation checks (only when generation is opted in) ──────
989
+ // ── 6. Repo-level default/placeholder marketplace-name check (always SOFT) ──
990
+ // Marketplace identity is a REPO-level concern, so this runs only when validating a repo root
991
+ // (not a single plugin directory — that path stays scoped to the one plugin and short-circuits
992
+ // on envelope-invalid). Independent of registry generation: the effective name comes from
993
+ // `aipm.workspace.ts` when present, else from a committed repo-root registry's top-level
994
+ // `name`/`owner.name`. A placeholder name collides with the upstream marketplace and strands
995
+ // plugins, so warn — but never fail (soft, does not flip `passed`).
996
+ if (path.resolve(targetPath) === repoRoot) {
997
+ findings.push(...checkDefaultMarketplaceName(repoRoot, workspace));
998
+ }
999
+ // ── 7. Repo-level generation checks (only when generation is opted in) ──────
898
1000
  // Registries AND single-artifact-host root artifacts are repo-scoped, so these run once per repo
899
1001
  // against EVERY plugin under the repo root — not just the plugins in `pluginDirs` (length-1 for
900
1002
  // single-plugin input). That mirrors how `runBuild` regenerates the repo-complete output, so a