@ai-plugin-marketplace/core 0.1.0 → 0.3.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 (61) hide show
  1. package/dist/config.d.ts +112 -0
  2. package/dist/config.d.ts.map +1 -1
  3. package/dist/config.js +56 -0
  4. package/dist/config.js.map +1 -1
  5. package/dist/core.d.ts +187 -4
  6. package/dist/index.d.ts +4 -4
  7. package/dist/index.d.ts.map +1 -1
  8. package/dist/index.js +2 -2
  9. package/dist/index.js.map +1 -1
  10. package/dist/pipeline/build.d.ts +159 -1
  11. package/dist/pipeline/build.d.ts.map +1 -1
  12. package/dist/pipeline/build.js +413 -9
  13. package/dist/pipeline/build.js.map +1 -1
  14. package/dist/pipeline/discover.d.ts +20 -13
  15. package/dist/pipeline/discover.d.ts.map +1 -1
  16. package/dist/pipeline/discover.js +53 -32
  17. package/dist/pipeline/discover.js.map +1 -1
  18. package/dist/pipeline/init-template.d.ts +16 -5
  19. package/dist/pipeline/init-template.d.ts.map +1 -1
  20. package/dist/pipeline/init-template.js +65 -21
  21. package/dist/pipeline/init-template.js.map +1 -1
  22. package/dist/pipeline/init.d.ts +17 -14
  23. package/dist/pipeline/init.d.ts.map +1 -1
  24. package/dist/pipeline/init.js +23 -15
  25. package/dist/pipeline/init.js.map +1 -1
  26. package/dist/pipeline/load-config.d.ts +62 -2
  27. package/dist/pipeline/load-config.d.ts.map +1 -1
  28. package/dist/pipeline/load-config.js +147 -30
  29. package/dist/pipeline/load-config.js.map +1 -1
  30. package/dist/pipeline/operations.d.ts +15 -3
  31. package/dist/pipeline/operations.d.ts.map +1 -1
  32. package/dist/pipeline/operations.js +25 -4
  33. package/dist/pipeline/operations.js.map +1 -1
  34. package/dist/pipeline/scaffold-refresh.d.ts +80 -0
  35. package/dist/pipeline/scaffold-refresh.d.ts.map +1 -0
  36. package/dist/pipeline/scaffold-refresh.js +152 -0
  37. package/dist/pipeline/scaffold-refresh.js.map +1 -0
  38. package/dist/pipeline/scaffold.d.ts.map +1 -1
  39. package/dist/pipeline/scaffold.js +55 -26
  40. package/dist/pipeline/scaffold.js.map +1 -1
  41. package/dist/pipeline/types.d.ts +51 -3
  42. package/dist/pipeline/types.d.ts.map +1 -1
  43. package/dist/pipeline/types.js +1 -0
  44. package/dist/pipeline/types.js.map +1 -1
  45. package/dist/pipeline/validate.d.ts +12 -2
  46. package/dist/pipeline/validate.d.ts.map +1 -1
  47. package/dist/pipeline/validate.js +342 -38
  48. package/dist/pipeline/validate.js.map +1 -1
  49. package/dist/targets/codex/scaffold.d.ts +31 -0
  50. package/dist/targets/codex/scaffold.d.ts.map +1 -0
  51. package/dist/targets/codex/scaffold.js +47 -0
  52. package/dist/targets/codex/scaffold.js.map +1 -0
  53. package/dist/targets/codex/schemas.d.ts +70 -0
  54. package/dist/targets/codex/schemas.d.ts.map +1 -0
  55. package/dist/targets/codex/schemas.js +153 -0
  56. package/dist/targets/codex/schemas.js.map +1 -0
  57. package/dist/targets/codex/validate.d.ts +25 -0
  58. package/dist/targets/codex/validate.d.ts.map +1 -0
  59. package/dist/targets/codex/validate.js +117 -0
  60. package/dist/targets/codex/validate.js.map +1 -0
  61. package/package.json +1 -1
@@ -1,70 +1,91 @@
1
1
  /**
2
2
  * Plugin discovery: resolve a `targetPath` (plugin dir or repo root) to the list of plugin
3
- * directories to operate on plus the `dist/` output root. Shared by the build (§5.2) and
3
+ * directories to operate on plus the dist output root. Shared by the build (§5.2) and
4
4
  * validate (§5.3) orchestrators so they detect topology identically.
5
5
  *
6
6
  * Detection rules (§8.1 "Why one build signature"):
7
- * - **Repo root** — `targetPath` contains a `plugins/` directory. Every immediate subdirectory
8
- * of `plugins/` that contains an `aipm.config.ts` is a plugin; `dist/` sits at `<root>/dist`.
7
+ * - **Repo root** — `targetPath` contains the configured plugins root (default `plugins/`), or an
8
+ * `aipm.repo.ts`. Every immediate subdirectory of the plugins root that contains an
9
+ * `aipm.config.ts` is a plugin; generated bundles sit at the configured dist root
10
+ * (default `<root>/dist`).
9
11
  * - **Single plugin** — otherwise `targetPath` is treated as a single plugin directory. The repo
10
- * root is the plugin's grandparent (`repoRoot = dirname(dirname(pluginDir))`) and `dist/` sits
11
- * there. A missing `aipm.config.ts` is **not** a discovery error: it is deferred to the
12
- * config loader so the validate orchestrator can report it as an `envelope-invalid` finding
13
- * (§10.1 step 1) and the build orchestrator can throw a descriptive error.
12
+ * root is the plugin's grandparent (`repoRoot = dirname(dirname(pluginDir))`) and the dist root
13
+ * is resolved from that repo root's config. A missing `aipm.config.ts` is **not** a discovery
14
+ * error: it is deferred to the config loader so the validate orchestrator can report it as an
15
+ * `envelope-invalid` finding (§10.1 step 1) and the build orchestrator can throw a descriptive
16
+ * error.
14
17
  *
15
18
  * Repo-root detection is tried first so a real repo root is never misread as a single plugin.
16
19
  *
20
+ * The plugins root and dist root are read from an optional `aipm.repo.ts` at the repo root
21
+ * (embedded-marketplace support). Absent that file, they default to `plugins/` and `dist/`, so a
22
+ * repo with no repo config behaves exactly as before.
23
+ *
17
24
  * @see docs/specs/architecture.md §8.1, §3.2 (repository topology), §10.1 (validation order)
18
25
  */
19
26
  import * as fs from 'node:fs';
20
27
  import * as path from 'node:path';
21
- import { AIPM_CONFIG_FILENAME } from './load-config.js';
28
+ import { AIPM_CONFIG_FILENAME, hasRepoConfig, loadRepoConfig } from './load-config.js';
22
29
  /** True iff `dir` contains an `aipm.config.ts`. */
23
30
  function hasConfig(dir) {
24
31
  return fs.existsSync(path.join(dir, AIPM_CONFIG_FILENAME));
25
32
  }
33
+ /** True iff `dir` exists and is a directory. */
34
+ function isDirectory(dir) {
35
+ return fs.existsSync(dir) && fs.statSync(dir).isDirectory();
36
+ }
26
37
  /**
27
- * Resolve `targetPath` to the plugins to build/validate and the `dist/` root.
38
+ * Resolve `targetPath` to the plugins to build/validate and the dist root.
28
39
  *
29
40
  * @param targetPath - Absolute path to a single plugin directory or a repo root.
30
41
  * @returns The resolved discovery.
31
- * @throws {Error} If `targetPath` is neither a plugin directory nor a repo root with a
32
- * `plugins/` directory.
42
+ * @throws {Error} If `targetPath` does not exist.
43
+ * @throws {ConfigLoadError} If a present `aipm.repo.ts` cannot be imported or fails validation.
33
44
  */
34
- export function discoverPlugins(targetPath) {
45
+ export async function discoverPlugins(targetPath) {
35
46
  const resolved = path.resolve(targetPath);
36
47
  if (!fs.existsSync(resolved)) {
37
48
  throw new Error(`Path '${targetPath}' does not exist.`);
38
49
  }
39
50
  // ── Repo-root input (tried first) ─────────────────────────────────────────
40
- // A path is a repo root when it does NOT itself carry a config but does contain a `plugins/`
41
- // directory. (A plugin directory that happens to contain a `plugins/` subfolder is vanishingly
42
- // unlikely and would be a layout error; carrying its own config disambiguates it as a plugin.)
43
- const pluginsDir = path.join(resolved, 'plugins');
44
- if (!hasConfig(resolved) && fs.existsSync(pluginsDir) && fs.statSync(pluginsDir).isDirectory()) {
45
- const pluginDirs = [];
46
- for (const entry of fs.readdirSync(pluginsDir, { withFileTypes: true })) {
47
- if (!entry.isDirectory())
48
- continue;
49
- const candidate = path.join(pluginsDir, entry.name);
50
- if (hasConfig(candidate)) {
51
- pluginDirs.push(candidate);
51
+ // A path is a repo root when it does NOT itself carry a plugin config but either declares an
52
+ // `aipm.repo.ts` or contains the configured plugins root. (A plugin directory that happens to
53
+ // contain a `plugins/` subfolder is vanishingly unlikely and would be a layout error; carrying
54
+ // its own `aipm.config.ts` disambiguates it as a plugin.)
55
+ if (!hasConfig(resolved)) {
56
+ const repoConfig = await loadRepoConfig(resolved);
57
+ const pluginsDir = path.join(resolved, repoConfig.pluginsRoot);
58
+ // An explicit `aipm.repo.ts` marks a repo root even before its plugins dir exists, so a
59
+ // freshly-initialised embedded repo still resolves as a repo root rather than a single plugin.
60
+ if (hasRepoConfig(resolved) || isDirectory(pluginsDir)) {
61
+ const pluginDirs = [];
62
+ if (isDirectory(pluginsDir)) {
63
+ for (const entry of fs.readdirSync(pluginsDir, { withFileTypes: true })) {
64
+ if (!entry.isDirectory())
65
+ continue;
66
+ const candidate = path.join(pluginsDir, entry.name);
67
+ if (hasConfig(candidate)) {
68
+ pluginDirs.push(candidate);
69
+ }
70
+ }
71
+ pluginDirs.sort();
52
72
  }
73
+ return {
74
+ repoRoot: resolved,
75
+ distDir: path.join(resolved, repoConfig.distDir),
76
+ pluginDirs,
77
+ };
53
78
  }
54
- pluginDirs.sort();
55
- return {
56
- repoRoot: resolved,
57
- distDir: path.join(resolved, 'dist'),
58
- pluginDirs,
59
- };
60
79
  }
61
80
  // ── Single-plugin input ───────────────────────────────────────────────────
62
81
  // Whether or not it carries a config: a missing config is deferred to the loader so validate
63
- // can emit `envelope-invalid` and build can throw a descriptive error (§10.1 step 1).
82
+ // can emit `envelope-invalid` and build can throw a descriptive error (§10.1 step 1). The dist
83
+ // root is resolved from the repo root's config so a relocated dist root is still honored.
64
84
  const grandparent = path.dirname(path.dirname(resolved));
85
+ const repoConfig = await loadRepoConfig(grandparent);
65
86
  return {
66
87
  repoRoot: grandparent,
67
- distDir: path.join(grandparent, 'dist'),
88
+ distDir: path.join(grandparent, repoConfig.distDir),
68
89
  pluginDirs: [resolved],
69
90
  };
70
91
  }
@@ -1 +1 @@
1
- {"version":3,"file":"discover.js","sourceRoot":"","sources":["../../src/pipeline/discover.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,KAAK,EAAE,MAAM,SAAS,CAAC;AAC9B,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAElC,OAAO,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AAYxD,mDAAmD;AACnD,SAAS,SAAS,CAAC,GAAW;IAC5B,OAAO,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,oBAAoB,CAAC,CAAC,CAAC;AAC7D,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,eAAe,CAAC,UAAkB;IAChD,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;IAE1C,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC7B,MAAM,IAAI,KAAK,CAAC,SAAS,UAAU,mBAAmB,CAAC,CAAC;IAC1D,CAAC;IAED,6EAA6E;IAC7E,6FAA6F;IAC7F,+FAA+F;IAC/F,+FAA+F;IAC/F,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,SAAS,CAAC,CAAC;IAClD,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC,UAAU,CAAC,UAAU,CAAC,IAAI,EAAE,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC,WAAW,EAAE,EAAE,CAAC;QAC/F,MAAM,UAAU,GAAa,EAAE,CAAC;QAChC,KAAK,MAAM,KAAK,IAAI,EAAE,CAAC,WAAW,CAAC,UAAU,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC;YACxE,IAAI,CAAC,KAAK,CAAC,WAAW,EAAE;gBAAE,SAAS;YACnC,MAAM,SAAS,GAAG,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;YACpD,IAAI,SAAS,CAAC,SAAS,CAAC,EAAE,CAAC;gBACzB,UAAU,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;YAC7B,CAAC;QACH,CAAC;QACD,UAAU,CAAC,IAAI,EAAE,CAAC;QAClB,OAAO;YACL,QAAQ,EAAE,QAAQ;YAClB,OAAO,EAAE,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,MAAM,CAAC;YACpC,UAAU;SACX,CAAC;IACJ,CAAC;IAED,6EAA6E;IAC7E,6FAA6F;IAC7F,sFAAsF;IACtF,MAAM,WAAW,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC;IACzD,OAAO;QACL,QAAQ,EAAE,WAAW;QACrB,OAAO,EAAE,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,MAAM,CAAC;QACvC,UAAU,EAAE,CAAC,QAAQ,CAAC;KACvB,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"discover.js","sourceRoot":"","sources":["../../src/pipeline/discover.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,OAAO,KAAK,EAAE,MAAM,SAAS,CAAC;AAC9B,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAElC,OAAO,EAAE,oBAAoB,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAYvF,mDAAmD;AACnD,SAAS,SAAS,CAAC,GAAW;IAC5B,OAAO,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,oBAAoB,CAAC,CAAC,CAAC;AAC7D,CAAC;AAED,gDAAgD;AAChD,SAAS,WAAW,CAAC,GAAW;IAC9B,OAAO,EAAE,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,WAAW,EAAE,CAAC;AAC9D,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,UAAkB;IACtD,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;IAE1C,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC7B,MAAM,IAAI,KAAK,CAAC,SAAS,UAAU,mBAAmB,CAAC,CAAC;IAC1D,CAAC;IAED,6EAA6E;IAC7E,6FAA6F;IAC7F,8FAA8F;IAC9F,+FAA+F;IAC/F,0DAA0D;IAC1D,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,EAAE,CAAC;QACzB,MAAM,UAAU,GAAG,MAAM,cAAc,CAAC,QAAQ,CAAC,CAAC;QAClD,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,UAAU,CAAC,WAAW,CAAC,CAAC;QAC/D,wFAAwF;QACxF,+FAA+F;QAC/F,IAAI,aAAa,CAAC,QAAQ,CAAC,IAAI,WAAW,CAAC,UAAU,CAAC,EAAE,CAAC;YACvD,MAAM,UAAU,GAAa,EAAE,CAAC;YAChC,IAAI,WAAW,CAAC,UAAU,CAAC,EAAE,CAAC;gBAC5B,KAAK,MAAM,KAAK,IAAI,EAAE,CAAC,WAAW,CAAC,UAAU,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC;oBACxE,IAAI,CAAC,KAAK,CAAC,WAAW,EAAE;wBAAE,SAAS;oBACnC,MAAM,SAAS,GAAG,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;oBACpD,IAAI,SAAS,CAAC,SAAS,CAAC,EAAE,CAAC;wBACzB,UAAU,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;oBAC7B,CAAC;gBACH,CAAC;gBACD,UAAU,CAAC,IAAI,EAAE,CAAC;YACpB,CAAC;YACD,OAAO;gBACL,QAAQ,EAAE,QAAQ;gBAClB,OAAO,EAAE,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,UAAU,CAAC,OAAO,CAAC;gBAChD,UAAU;aACX,CAAC;QACJ,CAAC;IACH,CAAC;IAED,6EAA6E;IAC7E,6FAA6F;IAC7F,+FAA+F;IAC/F,0FAA0F;IAC1F,MAAM,WAAW,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC;IACzD,MAAM,UAAU,GAAG,MAAM,cAAc,CAAC,WAAW,CAAC,CAAC;IACrD,OAAO;QACL,QAAQ,EAAE,WAAW;QACrB,OAAO,EAAE,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,UAAU,CAAC,OAAO,CAAC;QACnD,UAAU,EAAE,CAAC,QAAQ,CAAC;KACvB,CAAC;AACJ,CAAC"}
@@ -21,13 +21,24 @@ export interface InitFile {
21
21
  /** Full file contents, including a trailing newline. */
22
22
  content: string;
23
23
  }
24
+ /**
25
+ * Toolkit-owned **scaffold** files that `aipm init --refresh` keeps in sync with the installed
26
+ * tooling: the CI workflow and `.gitignore`. These are pure tooling recipes — their content is
27
+ * independent of the repo name and the pinned toolkit version, so refresh can re-render them with
28
+ * no inputs and compare byte-for-byte. (Files with repo identity or user content — `package.json`,
29
+ * `aipm.workspace.ts`, `README.md`, plugins, `aipm build` output — are deliberately NOT here.)
30
+ *
31
+ * Output is deterministic and stably ordered.
32
+ */
33
+ export declare function buildManagedScaffoldFiles(): InitFile[];
24
34
  /**
25
35
  * Build the complete, deterministic seed file set for a consumer repo named `name`, pinning the
26
- * `@ai-plugin-marketplace/cli` dev dependency to `^${toolkitVersion}`.
36
+ * `cli`/`core` dev dependencies to carets of `cliVersion`/`coreVersion` respectively.
27
37
  *
28
- * The set mirrors §3.2: `package.json`, `.gitignore`, `README.md`, both repo-root marketplace
29
- * registries, an empty `plugins/` (seeded with `.gitkeep` so the directory is tracked), and the
30
- * CI workflow. Output is a pure function of the two inputs stable ordering, no timestamps.
38
+ * The set mirrors §3.2: `package.json`, the {@link buildManagedScaffoldFiles managed scaffold
39
+ * files} (`.gitignore`, CI workflow), `README.md`, both repo-root marketplace registries, and an
40
+ * empty `plugins/` (seeded with `.gitkeep` so the directory is tracked). Output is a pure function
41
+ * of the inputs — stable ordering, no timestamps.
31
42
  */
32
- export declare function buildInitFiles(name: string, toolkitVersion: string): InitFile[];
43
+ export declare function buildInitFiles(name: string, cliVersion: string, coreVersion: string): InitFile[];
33
44
  //# sourceMappingURL=init-template.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"init-template.d.ts","sourceRoot":"","sources":["../../src/pipeline/init-template.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAMH,gGAAgG;AAChG,MAAM,WAAW,QAAQ;IACvB,yDAAyD;IACzD,IAAI,EAAE,MAAM,CAAC;IACb,wDAAwD;IACxD,OAAO,EAAE,MAAM,CAAC;CACjB;AA8GD;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,GAAG,QAAQ,EAAE,CAU/E"}
1
+ {"version":3,"file":"init-template.d.ts","sourceRoot":"","sources":["../../src/pipeline/init-template.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAaH,gGAAgG;AAChG,MAAM,WAAW,QAAQ;IACvB,yDAAyD;IACzD,IAAI,EAAE,MAAM,CAAC;IACb,wDAAwD;IACxD,OAAO,EAAE,MAAM,CAAC;CACjB;AAqID;;;;;;;;GAQG;AACH,wBAAgB,yBAAyB,IAAI,QAAQ,EAAE,CAKtD;AAED;;;;;;;;GAQG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,QAAQ,EAAE,CAShG"}
@@ -17,33 +17,40 @@
17
17
  const json = String.raw;
18
18
  const md = String.raw;
19
19
  const yaml = String.raw;
20
+ /**
21
+ * pnpm version pinned in the generated `package.json#packageManager`. The CI workflow reads the
22
+ * pnpm version from this field (it runs `pnpm/action-setup` without a hard-coded version), so the
23
+ * two stay in sync. Bumped deliberately when the recommended pnpm baseline moves.
24
+ */
25
+ const PACKAGE_MANAGER = 'pnpm@10.30.3';
20
26
  /**
21
27
  * The `package.json` for a generated consumer repo.
22
28
  *
23
29
  * - `private: true` — a plugin repo is never published to npm; only its plugins ship to registries.
24
30
  * - `type: 'module'` — the toolkit is ESM-only (§8.1), and `aipm.config.ts` files are ESM.
31
+ * - `packageManager` pins pnpm; the CI workflow reads the pnpm version from here.
25
32
  * - `scripts` call `aipm` directly (it is on PATH via the dev dependency's bin).
26
- * - The `@ai-plugin-marketplace/cli` dev dependency is pinned to `^<toolkitVersion>` so authors
27
- * upgrade the whole toolkit in lockstep via `pnpm up` (§9.1 lockstep release, §11 contract).
33
+ * - `cli` provides the `aipm` binary; `core` provides `defineConfig`/`defineWorkspace` for each
34
+ * plugin's `aipm.config.ts` (§6.1). They ship independently and may differ (`cli 0.1.1` ships
35
+ * with `core 0.2.0`), so each is pinned to a caret of its own version; authors upgrade both via
36
+ * `pnpm up` (§11 contract).
28
37
  *
29
38
  * Emitted as 2-space JSON with a trailing newline to match the repo's formatting conventions.
30
39
  */
31
- function renderPackageJson(name, toolkitVersion) {
40
+ function renderPackageJson(name, cliVersion, coreVersion) {
32
41
  const pkg = {
33
42
  name,
34
43
  private: true,
35
44
  type: 'module',
45
+ packageManager: PACKAGE_MANAGER,
36
46
  scripts: {
37
47
  build: 'aipm build',
38
48
  check: 'aipm validate',
39
49
  scaffold: 'aipm scaffold',
40
50
  },
41
51
  devDependencies: {
42
- // cli provides the `aipm` binary; core provides `defineConfig` for each
43
- // plugin's `aipm.config.ts` import (§6.1). Both pinned to the same toolkit
44
- // version and upgraded together via `pnpm up`.
45
- '@ai-plugin-marketplace/cli': `^${toolkitVersion}`,
46
- '@ai-plugin-marketplace/core': `^${toolkitVersion}`,
52
+ '@ai-plugin-marketplace/cli': `^${cliVersion}`,
53
+ '@ai-plugin-marketplace/core': `^${coreVersion}`,
47
54
  },
48
55
  };
49
56
  return `${JSON.stringify(pkg, null, 2)}\n`;
@@ -95,21 +102,43 @@ function renderCiWorkflow() {
95
102
 
96
103
  on:
97
104
  push:
105
+ branches: [main]
98
106
  pull_request:
107
+ branches: [main]
99
108
 
100
109
  jobs:
101
- build-and-validate:
110
+ CI:
102
111
  runs-on: ubuntu-latest
112
+ env:
113
+ CI: "true"
103
114
  steps:
104
115
  - uses: actions/checkout@v4
116
+
105
117
  - uses: pnpm/action-setup@v4
118
+ # version is read from package.json#packageManager — do not duplicate here
119
+
106
120
  - uses: actions/setup-node@v4
107
121
  with:
108
- node-version: 20
122
+ node-version: 24
109
123
  cache: pnpm
110
- - run: pnpm install --frozen-lockfile
111
- - run: pnpm exec aipm build
112
- - run: pnpm exec aipm validate
124
+
125
+ - name: Install dependencies
126
+ run: pnpm install --frozen-lockfile
127
+
128
+ - name: Build plugins
129
+ run: pnpm exec aipm build
130
+
131
+ - name: Verify the tree is clean after build (freshness)
132
+ run: |
133
+ if [ -n "$(git status --porcelain)" ]; then
134
+ echo "::error::Working tree is dirty after 'aipm build'. Run 'aipm build' locally and commit the regenerated artifacts."
135
+ git status --porcelain
136
+ git --no-pager diff
137
+ exit 1
138
+ fi
139
+
140
+ - name: Validate plugins
141
+ run: pnpm exec aipm validate
113
142
  `;
114
143
  }
115
144
  /** `.gitignore` for a consumer repo: dependencies, build intermediates, OS/local cruft. */
@@ -121,22 +150,37 @@ function renderGitignore() {
121
150
  `;
122
151
  }
123
152
  /**
124
- * Build the complete, deterministic seed file set for a consumer repo named `name`, pinning the
125
- * `@ai-plugin-marketplace/cli` dev dependency to `^${toolkitVersion}`.
153
+ * Toolkit-owned **scaffold** files that `aipm init --refresh` keeps in sync with the installed
154
+ * tooling: the CI workflow and `.gitignore`. These are pure tooling recipes — their content is
155
+ * independent of the repo name and the pinned toolkit version, so refresh can re-render them with
156
+ * no inputs and compare byte-for-byte. (Files with repo identity or user content — `package.json`,
157
+ * `aipm.workspace.ts`, `README.md`, plugins, `aipm build` output — are deliberately NOT here.)
126
158
  *
127
- * The set mirrors §3.2: `package.json`, `.gitignore`, `README.md`, both repo-root marketplace
128
- * registries, an empty `plugins/` (seeded with `.gitkeep` so the directory is tracked), and the
129
- * CI workflow. Output is a pure function of the two inputs — stable ordering, no timestamps.
159
+ * Output is deterministic and stably ordered.
130
160
  */
131
- export function buildInitFiles(name, toolkitVersion) {
161
+ export function buildManagedScaffoldFiles() {
132
162
  return [
133
- { path: 'package.json', content: renderPackageJson(name, toolkitVersion) },
134
163
  { path: '.gitignore', content: renderGitignore() },
164
+ { path: '.github/workflows/ci.yml', content: renderCiWorkflow() },
165
+ ];
166
+ }
167
+ /**
168
+ * Build the complete, deterministic seed file set for a consumer repo named `name`, pinning the
169
+ * `cli`/`core` dev dependencies to carets of `cliVersion`/`coreVersion` respectively.
170
+ *
171
+ * The set mirrors §3.2: `package.json`, the {@link buildManagedScaffoldFiles managed scaffold
172
+ * files} (`.gitignore`, CI workflow), `README.md`, both repo-root marketplace registries, and an
173
+ * empty `plugins/` (seeded with `.gitkeep` so the directory is tracked). Output is a pure function
174
+ * of the inputs — stable ordering, no timestamps.
175
+ */
176
+ export function buildInitFiles(name, cliVersion, coreVersion) {
177
+ return [
178
+ { path: 'package.json', content: renderPackageJson(name, cliVersion, coreVersion) },
135
179
  { path: 'README.md', content: renderReadme(name) },
136
180
  { path: '.claude-plugin/marketplace.json', content: renderEmptyMarketplace() },
137
181
  { path: '.cursor-plugin/marketplace.json', content: renderEmptyMarketplace() },
138
182
  { path: 'plugins/.gitkeep', content: '' },
139
- { path: '.github/workflows/ci.yml', content: renderCiWorkflow() },
183
+ ...buildManagedScaffoldFiles(),
140
184
  ];
141
185
  }
142
186
  //# sourceMappingURL=init-template.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"init-template.js","sourceRoot":"","sources":["../../src/pipeline/init-template.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,MAAM,IAAI,GAAG,MAAM,CAAC,GAAG,CAAC;AACxB,MAAM,EAAE,GAAG,MAAM,CAAC,GAAG,CAAC;AACtB,MAAM,IAAI,GAAG,MAAM,CAAC,GAAG,CAAC;AAUxB;;;;;;;;;;GAUG;AACH,SAAS,iBAAiB,CAAC,IAAY,EAAE,cAAsB;IAC7D,MAAM,GAAG,GAAG;QACV,IAAI;QACJ,OAAO,EAAE,IAAI;QACb,IAAI,EAAE,QAAQ;QACd,OAAO,EAAE;YACP,KAAK,EAAE,YAAY;YACnB,KAAK,EAAE,eAAe;YACtB,QAAQ,EAAE,eAAe;SAC1B;QACD,eAAe,EAAE;YACf,wEAAwE;YACxE,2EAA2E;YAC3E,+CAA+C;YAC/C,4BAA4B,EAAE,IAAI,cAAc,EAAE;YAClD,6BAA6B,EAAE,IAAI,cAAc,EAAE;SACpD;KACF,CAAC;IACF,OAAO,GAAG,IAAI,CAAC,SAAS,CAAC,GAAG,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC;AAC7C,CAAC;AAED,+FAA+F;AAC/F,SAAS,sBAAsB;IAC7B,OAAO,IAAI,CAAA;;;CAGZ,CAAC;AACF,CAAC;AAED,2EAA2E;AAC3E,SAAS,YAAY,CAAC,IAAY;IAChC,OAAO,EAAE,CAAA,KAAK,IAAI;;;;;;;;;;;;;;;;;;;;;;;;CAwBnB,CAAC;AACF,CAAC;AAED;;;;;;GAMG;AACH,SAAS,gBAAgB;IACvB,OAAO,IAAI,CAAA;;;;;;;;;;;;;;;;;;;CAmBZ,CAAC;AACF,CAAC;AAED,2FAA2F;AAC3F,SAAS,eAAe;IACtB,OAAO;;;;CAIR,CAAC;AACF,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,cAAc,CAAC,IAAY,EAAE,cAAsB;IACjE,OAAO;QACL,EAAE,IAAI,EAAE,cAAc,EAAE,OAAO,EAAE,iBAAiB,CAAC,IAAI,EAAE,cAAc,CAAC,EAAE;QAC1E,EAAE,IAAI,EAAE,YAAY,EAAE,OAAO,EAAE,eAAe,EAAE,EAAE;QAClD,EAAE,IAAI,EAAE,WAAW,EAAE,OAAO,EAAE,YAAY,CAAC,IAAI,CAAC,EAAE;QAClD,EAAE,IAAI,EAAE,iCAAiC,EAAE,OAAO,EAAE,sBAAsB,EAAE,EAAE;QAC9E,EAAE,IAAI,EAAE,iCAAiC,EAAE,OAAO,EAAE,sBAAsB,EAAE,EAAE;QAC9E,EAAE,IAAI,EAAE,kBAAkB,EAAE,OAAO,EAAE,EAAE,EAAE;QACzC,EAAE,IAAI,EAAE,0BAA0B,EAAE,OAAO,EAAE,gBAAgB,EAAE,EAAE;KAClE,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"init-template.js","sourceRoot":"","sources":["../../src/pipeline/init-template.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,MAAM,IAAI,GAAG,MAAM,CAAC,GAAG,CAAC;AACxB,MAAM,EAAE,GAAG,MAAM,CAAC,GAAG,CAAC;AACtB,MAAM,IAAI,GAAG,MAAM,CAAC,GAAG,CAAC;AAExB;;;;GAIG;AACH,MAAM,eAAe,GAAG,cAAc,CAAC;AAUvC;;;;;;;;;;;;;GAaG;AACH,SAAS,iBAAiB,CAAC,IAAY,EAAE,UAAkB,EAAE,WAAmB;IAC9E,MAAM,GAAG,GAAG;QACV,IAAI;QACJ,OAAO,EAAE,IAAI;QACb,IAAI,EAAE,QAAQ;QACd,cAAc,EAAE,eAAe;QAC/B,OAAO,EAAE;YACP,KAAK,EAAE,YAAY;YACnB,KAAK,EAAE,eAAe;YACtB,QAAQ,EAAE,eAAe;SAC1B;QACD,eAAe,EAAE;YACf,4BAA4B,EAAE,IAAI,UAAU,EAAE;YAC9C,6BAA6B,EAAE,IAAI,WAAW,EAAE;SACjD;KACF,CAAC;IACF,OAAO,GAAG,IAAI,CAAC,SAAS,CAAC,GAAG,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC;AAC7C,CAAC;AAED,+FAA+F;AAC/F,SAAS,sBAAsB;IAC7B,OAAO,IAAI,CAAA;;;CAGZ,CAAC;AACF,CAAC;AAED,2EAA2E;AAC3E,SAAS,YAAY,CAAC,IAAY;IAChC,OAAO,EAAE,CAAA,KAAK,IAAI;;;;;;;;;;;;;;;;;;;;;;;;CAwBnB,CAAC;AACF,CAAC;AAED;;;;;;GAMG;AACH,SAAS,gBAAgB;IACvB,OAAO,IAAI,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAyCZ,CAAC;AACF,CAAC;AAED,2FAA2F;AAC3F,SAAS,eAAe;IACtB,OAAO;;;;CAIR,CAAC;AACF,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,yBAAyB;IACvC,OAAO;QACL,EAAE,IAAI,EAAE,YAAY,EAAE,OAAO,EAAE,eAAe,EAAE,EAAE;QAClD,EAAE,IAAI,EAAE,0BAA0B,EAAE,OAAO,EAAE,gBAAgB,EAAE,EAAE;KAClE,CAAC;AACJ,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,cAAc,CAAC,IAAY,EAAE,UAAkB,EAAE,WAAmB;IAClF,OAAO;QACL,EAAE,IAAI,EAAE,cAAc,EAAE,OAAO,EAAE,iBAAiB,CAAC,IAAI,EAAE,UAAU,EAAE,WAAW,CAAC,EAAE;QACnF,EAAE,IAAI,EAAE,WAAW,EAAE,OAAO,EAAE,YAAY,CAAC,IAAI,CAAC,EAAE;QAClD,EAAE,IAAI,EAAE,iCAAiC,EAAE,OAAO,EAAE,sBAAsB,EAAE,EAAE;QAC9E,EAAE,IAAI,EAAE,iCAAiC,EAAE,OAAO,EAAE,sBAAsB,EAAE,EAAE;QAC9E,EAAE,IAAI,EAAE,kBAAkB,EAAE,OAAO,EAAE,EAAE,EAAE;QACzC,GAAG,yBAAyB,EAAE;KAC/B,CAAC;AACJ,CAAC"}
@@ -4,35 +4,38 @@
4
4
  *
5
5
  * This is the filesystem-writing orchestrator behind the public `init` operation and the
6
6
  * `aipm init [dir]` CLI surface. The generated file *contents* live in `init-template.ts`; this
7
- * module is the I/O boundary: it resolves the toolkit version to pin, refuses to clobber a
8
- * non-empty target, and writes the seed tree.
7
+ * module is the I/O boundary: it resolves the versions to pin, refuses to clobber a non-empty
8
+ * target, writes the seed tree, and seeds the `.aipm/scaffold.json` refresh sidecar.
9
9
  *
10
- * **Version pinning (§9.1 lockstep release).** core and cli ship in lockstep, so the `cli` dev
11
- * dependency is pinned to a caret of core's *own* version — read at runtime from this package's
12
- * `package.json`, resolved relative to {@link import.meta.url} exactly as `load-config.ts` resolves
13
- * the package entrypoint. Today that yields `^0.1.0-alpha.0`; once 0.1.0 ships, an `init` run from
14
- * the published cli pins `^0.1.0`.
10
+ * **Version pinning.** `cli` and `core` ship independently and may differ (e.g. `cli 0.1.1` ships
11
+ * with `core 0.2.0`), so the generated `package.json` pins each to a caret of its *own* version.
12
+ * `core`'s version is read at runtime from this package's `package.json` (resolved relative to
13
+ * {@link import.meta.url}, exactly as `load-config.ts` resolves the package entrypoint); `cli`'s
14
+ * version is supplied by the cli entrypoint via {@link InitOptions.cliVersion} (it reads its own
15
+ * `package.json`). When `cliVersion` is omitted, it falls back to core's version.
15
16
  *
16
17
  * @see docs/specs/architecture.md §3.2 (template repo contents)
17
- * @see docs/specs/architecture.md §9.1 (lockstep release lines)
18
18
  * @see docs/specs/architecture.md §11 (template→toolkit dependency contract)
19
+ * @see docs/specs/scaffold-refresh-and-upgrade.md (`aipm init --refresh`)
19
20
  */
20
21
  import type { InitOptions } from './types.js';
21
22
  /**
22
23
  * Scaffold a thin consumer repo at `targetDir` (§3.2).
23
24
  *
24
- * Writes `package.json` (with the `@ai-plugin-marketplace/cli` dev dependency pinned to a caret of
25
- * the current toolkit version), `.gitignore`, `README.md`, both repo-root marketplace registries
26
- * (`{ "plugins": [] }`), an empty `plugins/` (tracked via `.gitkeep`), and a CI workflow that runs
27
- * `aipm build` then `aipm validate` (§10.5 freshness). The repo name defaults to
28
- * `basename(targetDir)`; override it with `opts.name`.
25
+ * Writes `package.json` (with `cli`/`core` dev dependencies pinned to carets of their respective
26
+ * versions), `.gitignore`, `README.md`, both repo-root marketplace registries (`{ "plugins": [] }`),
27
+ * an empty `plugins/` (tracked via `.gitkeep`), and a CI workflow that runs `aipm build` then
28
+ * `aipm validate` (§10.5 freshness). It also seeds `.aipm/scaffold.json` so a later
29
+ * `aipm init --refresh` can tell pristine toolkit-owned files from user edits. The repo name
30
+ * defaults to `basename(targetDir)`; override it with `opts.name`.
29
31
  *
30
32
  * **Refuses to clobber.** If `targetDir` exists and is a non-empty directory (or exists as a
31
33
  * non-directory), the function throws and writes nothing. Creating into a fresh or empty directory
32
34
  * is fine.
33
35
  *
34
36
  * @param targetDir - Absolute or relative path to the directory to scaffold into.
35
- * @param opts - Init options; `name` overrides the derived repo name.
37
+ * @param opts - Init options; `name` overrides the derived repo name, `cliVersion` the pinned cli
38
+ * dependency version (defaults to core's version).
36
39
  * @throws {Error} When `targetDir` exists and is non-empty (or is not a directory).
37
40
  */
38
41
  export declare function runInit(targetDir: string, opts?: InitOptions): Promise<void>;
@@ -1 +1 @@
1
- {"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../src/pipeline/init.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAOH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AA6B9C;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAsB,OAAO,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,GAAE,WAAgB,GAAG,OAAO,CAAC,IAAI,CAAC,CAqBtF"}
1
+ {"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../src/pipeline/init.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAQH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AA6B9C;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAsB,OAAO,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,GAAE,WAAgB,GAAG,OAAO,CAAC,IAAI,CAAC,CA0BtF"}
@@ -4,23 +4,25 @@
4
4
  *
5
5
  * This is the filesystem-writing orchestrator behind the public `init` operation and the
6
6
  * `aipm init [dir]` CLI surface. The generated file *contents* live in `init-template.ts`; this
7
- * module is the I/O boundary: it resolves the toolkit version to pin, refuses to clobber a
8
- * non-empty target, and writes the seed tree.
7
+ * module is the I/O boundary: it resolves the versions to pin, refuses to clobber a non-empty
8
+ * target, writes the seed tree, and seeds the `.aipm/scaffold.json` refresh sidecar.
9
9
  *
10
- * **Version pinning (§9.1 lockstep release).** core and cli ship in lockstep, so the `cli` dev
11
- * dependency is pinned to a caret of core's *own* version — read at runtime from this package's
12
- * `package.json`, resolved relative to {@link import.meta.url} exactly as `load-config.ts` resolves
13
- * the package entrypoint. Today that yields `^0.1.0-alpha.0`; once 0.1.0 ships, an `init` run from
14
- * the published cli pins `^0.1.0`.
10
+ * **Version pinning.** `cli` and `core` ship independently and may differ (e.g. `cli 0.1.1` ships
11
+ * with `core 0.2.0`), so the generated `package.json` pins each to a caret of its *own* version.
12
+ * `core`'s version is read at runtime from this package's `package.json` (resolved relative to
13
+ * {@link import.meta.url}, exactly as `load-config.ts` resolves the package entrypoint); `cli`'s
14
+ * version is supplied by the cli entrypoint via {@link InitOptions.cliVersion} (it reads its own
15
+ * `package.json`). When `cliVersion` is omitted, it falls back to core's version.
15
16
  *
16
17
  * @see docs/specs/architecture.md §3.2 (template repo contents)
17
- * @see docs/specs/architecture.md §9.1 (lockstep release lines)
18
18
  * @see docs/specs/architecture.md §11 (template→toolkit dependency contract)
19
+ * @see docs/specs/scaffold-refresh-and-upgrade.md (`aipm init --refresh`)
19
20
  */
20
21
  import * as fs from 'node:fs';
21
22
  import * as path from 'node:path';
22
23
  import { fileURLToPath } from 'node:url';
23
24
  import { buildInitFiles } from './init-template.js';
25
+ import { writeScaffoldSidecar } from './scaffold-refresh.js';
24
26
  /**
25
27
  * Read this package's `package.json#version`, resolved relative to this module's location.
26
28
  *
@@ -51,18 +53,20 @@ function isFreshTarget(dir) {
51
53
  /**
52
54
  * Scaffold a thin consumer repo at `targetDir` (§3.2).
53
55
  *
54
- * Writes `package.json` (with the `@ai-plugin-marketplace/cli` dev dependency pinned to a caret of
55
- * the current toolkit version), `.gitignore`, `README.md`, both repo-root marketplace registries
56
- * (`{ "plugins": [] }`), an empty `plugins/` (tracked via `.gitkeep`), and a CI workflow that runs
57
- * `aipm build` then `aipm validate` (§10.5 freshness). The repo name defaults to
58
- * `basename(targetDir)`; override it with `opts.name`.
56
+ * Writes `package.json` (with `cli`/`core` dev dependencies pinned to carets of their respective
57
+ * versions), `.gitignore`, `README.md`, both repo-root marketplace registries (`{ "plugins": [] }`),
58
+ * an empty `plugins/` (tracked via `.gitkeep`), and a CI workflow that runs `aipm build` then
59
+ * `aipm validate` (§10.5 freshness). It also seeds `.aipm/scaffold.json` so a later
60
+ * `aipm init --refresh` can tell pristine toolkit-owned files from user edits. The repo name
61
+ * defaults to `basename(targetDir)`; override it with `opts.name`.
59
62
  *
60
63
  * **Refuses to clobber.** If `targetDir` exists and is a non-empty directory (or exists as a
61
64
  * non-directory), the function throws and writes nothing. Creating into a fresh or empty directory
62
65
  * is fine.
63
66
  *
64
67
  * @param targetDir - Absolute or relative path to the directory to scaffold into.
65
- * @param opts - Init options; `name` overrides the derived repo name.
68
+ * @param opts - Init options; `name` overrides the derived repo name, `cliVersion` the pinned cli
69
+ * dependency version (defaults to core's version).
66
70
  * @throws {Error} When `targetDir` exists and is non-empty (or is not a directory).
67
71
  */
68
72
  export async function runInit(targetDir, opts = {}) {
@@ -72,13 +76,17 @@ export async function runInit(targetDir, opts = {}) {
72
76
  'Choose a new path or an empty directory.');
73
77
  }
74
78
  const name = opts.name ?? path.basename(resolved);
75
- const files = buildInitFiles(name, coreVersion());
79
+ const core = coreVersion();
80
+ const cli = opts.cliVersion ?? core;
81
+ const files = buildInitFiles(name, cli, core);
76
82
  fs.mkdirSync(resolved, { recursive: true });
77
83
  for (const file of files) {
78
84
  const full = path.join(resolved, file.path);
79
85
  fs.mkdirSync(path.dirname(full), { recursive: true });
80
86
  fs.writeFileSync(full, file.content, 'utf-8');
81
87
  }
88
+ // Seed the refresh sidecar so `aipm init --refresh` has a baseline of toolkit-owned content.
89
+ writeScaffoldSidecar(resolved);
82
90
  return Promise.resolve();
83
91
  }
84
92
  //# sourceMappingURL=init.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"init.js","sourceRoot":"","sources":["../../src/pipeline/init.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,KAAK,EAAE,MAAM,SAAS,CAAC;AAC9B,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAClC,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAEzC,OAAO,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAGpD;;;;;;;GAOG;AACH,SAAS,WAAW;IAClB,MAAM,IAAI,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAC5C,sFAAsF;IACtF,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,cAAc,CAAC,CAAC;IAC1E,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,YAAY,CAAC,OAAO,EAAE,OAAO,CAAC,CAAwB,CAAC;IACjF,OAAO,GAAG,CAAC,OAAO,CAAC;AACrB,CAAC;AAED;;;GAGG;AACH,SAAS,aAAa,CAAC,GAAW;IAChC,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,GAAG,CAAC;QAAE,OAAO,IAAI,CAAC;IACrC,MAAM,IAAI,GAAG,EAAE,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;IAC9B,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE;QAAE,OAAO,KAAK,CAAC;IACtC,OAAO,EAAE,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC;AAC1C,CAAC;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,CAAC,KAAK,UAAU,OAAO,CAAC,SAAiB,EAAE,OAAoB,EAAE;IACrE,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IAEzC,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC7B,MAAM,IAAI,KAAK,CACb,8BAA8B,QAAQ,oDAAoD;YACxF,0CAA0C,CAC7C,CAAC;IACJ,CAAC;IAED,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;IAClD,MAAM,KAAK,GAAG,cAAc,CAAC,IAAI,EAAE,WAAW,EAAE,CAAC,CAAC;IAElD,EAAE,CAAC,SAAS,CAAC,QAAQ,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC5C,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC;QAC5C,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QACtD,EAAE,CAAC,aAAa,CAAC,IAAI,EAAE,IAAI,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;IAChD,CAAC;IAED,OAAO,OAAO,CAAC,OAAO,EAAE,CAAC;AAC3B,CAAC"}
1
+ {"version":3,"file":"init.js","sourceRoot":"","sources":["../../src/pipeline/init.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,KAAK,EAAE,MAAM,SAAS,CAAC;AAC9B,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAClC,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAEzC,OAAO,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACpD,OAAO,EAAE,oBAAoB,EAAE,MAAM,uBAAuB,CAAC;AAG7D;;;;;;;GAOG;AACH,SAAS,WAAW;IAClB,MAAM,IAAI,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAC5C,sFAAsF;IACtF,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,cAAc,CAAC,CAAC;IAC1E,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,YAAY,CAAC,OAAO,EAAE,OAAO,CAAC,CAAwB,CAAC;IACjF,OAAO,GAAG,CAAC,OAAO,CAAC;AACrB,CAAC;AAED;;;GAGG;AACH,SAAS,aAAa,CAAC,GAAW;IAChC,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,GAAG,CAAC;QAAE,OAAO,IAAI,CAAC;IACrC,MAAM,IAAI,GAAG,EAAE,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;IAC9B,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE;QAAE,OAAO,KAAK,CAAC;IACtC,OAAO,EAAE,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC;AAC1C,CAAC;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,CAAC,KAAK,UAAU,OAAO,CAAC,SAAiB,EAAE,OAAoB,EAAE;IACrE,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IAEzC,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC7B,MAAM,IAAI,KAAK,CACb,8BAA8B,QAAQ,oDAAoD;YACxF,0CAA0C,CAC7C,CAAC;IACJ,CAAC;IAED,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;IAClD,MAAM,IAAI,GAAG,WAAW,EAAE,CAAC;IAC3B,MAAM,GAAG,GAAG,IAAI,CAAC,UAAU,IAAI,IAAI,CAAC;IACpC,MAAM,KAAK,GAAG,cAAc,CAAC,IAAI,EAAE,GAAG,EAAE,IAAI,CAAC,CAAC;IAE9C,EAAE,CAAC,SAAS,CAAC,QAAQ,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC5C,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC;QAC5C,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QACtD,EAAE,CAAC,aAAa,CAAC,IAAI,EAAE,IAAI,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;IAChD,CAAC;IAED,6FAA6F;IAC7F,oBAAoB,CAAC,QAAQ,CAAC,CAAC;IAE/B,OAAO,OAAO,CAAC,OAAO,EAAE,CAAC;AAC3B,CAAC"}
@@ -18,7 +18,7 @@
18
18
  * @see docs/specs/architecture.md §6.1 (envelope declaration), P6 (TypeScript end-to-end)
19
19
  * @see docs/specs/architecture.md §5.2, §5.3 (build/validate consume the loaded envelope)
20
20
  */
21
- import type { AipmConfig } from '../config.js';
21
+ import type { AipmConfig, AipmWorkspace } from '../config.js';
22
22
  /** The canonical config filename every plugin must provide (§6.1). */
23
23
  export declare const AIPM_CONFIG_FILENAME = "aipm.config.ts";
24
24
  /**
@@ -31,6 +31,21 @@ export declare class ConfigLoadError extends Error {
31
31
  cause?: unknown;
32
32
  });
33
33
  }
34
+ /**
35
+ * A per-invocation memo of loaded plugin configs, keyed by absolute plugin directory. Created
36
+ * fresh by an orchestrator (`runBuild`/`runValidate`) and threaded through the helpers it calls,
37
+ * so a single invocation transpiles each `aipm.config.ts` **once** instead of repeatedly (the
38
+ * build loop, registry collection, and post-build validate would otherwise each reload every
39
+ * config — an expensive jiti transpile with module/fs caching disabled).
40
+ *
41
+ * Intentionally **per-invocation, not a module-level cache**: configs are static within one CLI
42
+ * run but may change between runs (and tests overwrite-and-reload across invocations), so a
43
+ * longer-lived cache would risk serving stale parses. Each `createConfigCache()` lives only for
44
+ * the orchestrator call that created it.
45
+ */
46
+ export type ConfigCache = Map<string, AipmConfig>;
47
+ /** Create an empty {@link ConfigCache} for a single orchestrator invocation. */
48
+ export declare function createConfigCache(): ConfigCache;
34
49
  /**
35
50
  * Load and validate a plugin's `aipm.config.ts`, returning the branded {@link AipmConfig}.
36
51
  *
@@ -39,9 +54,54 @@ export declare class ConfigLoadError extends Error {
39
54
  * `defineConfig` in their source (e.g. exported a plain object literal).
40
55
  *
41
56
  * @param pluginDir - Absolute path to the `plugins/<name>/` directory.
57
+ * @param cache - Optional per-invocation memo (see {@link ConfigCache}). When supplied, a config
58
+ * already loaded in this invocation is returned without re-transpiling.
42
59
  * @returns The validated, branded config.
43
60
  * @throws {ConfigLoadError} If the file is absent, fails to import, has no default export, or
44
61
  * the default export fails schema validation.
45
62
  */
46
- export declare function loadPluginConfig(pluginDir: string): Promise<AipmConfig>;
63
+ export declare function loadPluginConfig(pluginDir: string, cache?: ConfigCache): Promise<AipmConfig>;
64
+ /**
65
+ * Resolved repo-level configuration: the plugins/dist roots after defaults are applied. These are
66
+ * repo-root-relative directory names that {@link discoverPlugins} joins onto the repo root.
67
+ */
68
+ export interface ResolvedRepoConfig {
69
+ /** Directory (relative to the repo root) holding plugin folders. Default: `'plugins'`. */
70
+ pluginsRoot: string;
71
+ /** Directory (relative to the repo root) for generated dist bundles. Default: `'dist'`. */
72
+ distDir: string;
73
+ }
74
+ /** The resolved config used when no `aipm.repo.ts` is present — i.e. the historical topology. */
75
+ export declare const DEFAULT_REPO_CONFIG: ResolvedRepoConfig;
76
+ /** True iff `repoRoot` contains an `aipm.repo.ts`. */
77
+ export declare function hasRepoConfig(repoRoot: string): boolean;
78
+ /**
79
+ * Load the optional `aipm.repo.ts` at `repoRoot`, returning the resolved plugins/dist roots.
80
+ *
81
+ * When the file is **absent**, returns {@link DEFAULT_REPO_CONFIG} — so a repo with no repo
82
+ * config is byte-identical to the historical behavior. When **present**, the module's `default`
83
+ * export is re-validated through `defineRepoConfig` (applying defaults, rejecting absolute/`..`
84
+ * paths and unknown keys).
85
+ *
86
+ * @param repoRoot - Absolute path to the repo root.
87
+ * @returns The resolved repo config.
88
+ * @throws {ConfigLoadError} If the file exists but cannot be imported or fails validation.
89
+ */
90
+ export declare function loadRepoConfig(repoRoot: string): Promise<ResolvedRepoConfig>;
91
+ /** True iff `repoRoot` contains an `aipm.workspace.ts` (i.e. registry generation is opted in). */
92
+ export declare function hasWorkspaceConfig(repoRoot: string): boolean;
93
+ /**
94
+ * Load the optional `aipm.workspace.ts` at `repoRoot`, returning the validated, branded
95
+ * {@link AipmWorkspace}.
96
+ *
97
+ * When the file is **absent**, returns `undefined` — the signal that this repo has NOT opted into
98
+ * registry generation (the toolkit then leaves the hand-authored registries alone). When
99
+ * **present**, the module's `default` export is re-validated through `defineWorkspace` so the
100
+ * result provably passed the canonical schema.
101
+ *
102
+ * @param repoRoot - Absolute path to the repo root.
103
+ * @returns The validated workspace config, or `undefined` when no `aipm.workspace.ts` exists.
104
+ * @throws {ConfigLoadError} If the file exists but cannot be imported or fails validation.
105
+ */
106
+ export declare function loadWorkspaceConfig(repoRoot: string): Promise<AipmWorkspace | undefined>;
47
107
  //# sourceMappingURL=load-config.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"load-config.d.ts","sourceRoot":"","sources":["../../src/pipeline/load-config.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AASH,OAAO,KAAK,EAAE,UAAU,EAAmB,MAAM,cAAc,CAAC;AAEhE,sEAAsE;AACtE,eAAO,MAAM,oBAAoB,mBAAmB,CAAC;AAKrD;;;;GAIG;AACH,qBAAa,eAAgB,SAAQ,KAAK;gBAC5B,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE;CAI3D;AAmBD;;;;;;;;;;;GAWG;AACH,wBAAsB,gBAAgB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,CA4C7E"}
1
+ {"version":3,"file":"load-config.d.ts","sourceRoot":"","sources":["../../src/pipeline/load-config.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AASH,OAAO,KAAK,EACV,UAAU,EAGV,aAAa,EAEd,MAAM,cAAc,CAAC;AAEtB,sEAAsE;AACtE,eAAO,MAAM,oBAAoB,mBAAmB,CAAC;AAoBrD;;;;GAIG;AACH,qBAAa,eAAgB,SAAQ,KAAK;gBAC5B,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE;CAI3D;AAuED;;;;;;;;;;;GAWG;AACH,MAAM,MAAM,WAAW,GAAG,GAAG,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;AAElD,gFAAgF;AAChF,wBAAgB,iBAAiB,IAAI,WAAW,CAE/C;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAsB,gBAAgB,CACpC,SAAS,EAAE,MAAM,EACjB,KAAK,CAAC,EAAE,WAAW,GAClB,OAAO,CAAC,UAAU,CAAC,CAyBrB;AAED;;;GAGG;AACH,MAAM,WAAW,kBAAkB;IACjC,0FAA0F;IAC1F,WAAW,EAAE,MAAM,CAAC;IACpB,2FAA2F;IAC3F,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,iGAAiG;AACjG,eAAO,MAAM,mBAAmB,EAAE,kBAAgE,CAAC;AAEnG,sDAAsD;AACtD,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAEvD;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAelF;AAED,kGAAkG;AAClG,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAE5D;AAED;;;;;;;;;;;;GAYG;AACH,wBAAsB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,GAAG,SAAS,CAAC,CAgB9F"}