@cleocode/caamp 2026.4.6 → 2026.4.9

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.
package/dist/index.d.ts CHANGED
@@ -1488,328 +1488,501 @@ interface InstructionUpdateSummary {
1488
1488
  declare function updateInstructionsSingleOperation(providers: Provider[], content: string, scope?: Scope, projectDir?: string): Promise<InstructionUpdateSummary>;
1489
1489
 
1490
1490
  /**
1491
- * Format utility functions
1492
- */
1493
- /**
1494
- * Deep merge two objects, with `source` values winning on conflict.
1495
- *
1496
- * Recursively merges nested plain objects. Arrays and non-object values from
1497
- * `source` overwrite `target` values.
1498
- *
1499
- * @param target - Base object to merge into
1500
- * @param source - Object with values that take precedence
1501
- * @returns A new merged object (does not mutate inputs)
1491
+ * Three-tier scope helper for Pi harness operations.
1502
1492
  *
1503
1493
  * @remarks
1504
- * Only plain objects are recursively merged. Arrays and primitive values from
1505
- * `source` replace `target` values outright. Neither input is mutated.
1506
- *
1507
- * @example
1508
- * ```typescript
1509
- * const merged = deepMerge({ a: 1, b: { c: 2 } }, { b: { d: 3 } });
1510
- * // { a: 1, b: { c: 2, d: 3 } }
1511
- * ```
1494
+ * Per ADR-035 §D1, CAAMP wraps Pi's native two-tier extension model
1495
+ * (project-local `.pi/extensions/` global `~/.pi/agent/extensions/`)
1496
+ * with a third tier for the CleoOS-managed cross-project hub. The
1497
+ * resulting hierarchy, in precedence order on reads:
1512
1498
  *
1513
- * @public
1514
- */
1515
- declare function deepMerge(target: Record<string, unknown>, source: Record<string, unknown>): Record<string, unknown>;
1516
- /**
1517
- * Get a nested value from an object using a dot-notation key path.
1499
+ * | Tier | Path | Who owns it |
1500
+ * | --------- | ---------------------------------------------- | ------------------------ |
1501
+ * | `project` | `<cwd>/.pi/<asset>/` | repository |
1502
+ * | `user` | `$PI_CODING_AGENT_DIR` or `~/.pi/agent/<asset>`| Pi itself |
1503
+ * | `global` | `$CLEO_HOME/pi-<asset>/` | CleoOS (this wrapper) |
1518
1504
  *
1519
- * @param obj - Object to traverse
1520
- * @param keyPath - Dot-separated key path (e.g. `"mcpServers"` or `"a.b.c"`)
1521
- * @returns The value at the key path, or `undefined` if not found
1505
+ * Pi's own discovery loader is NOT modified. The `global` tier is either
1506
+ * copied or symlinked into the `user` tier on first use (lazy
1507
+ * materialization), so Pi's native two-tier loader picks extensions up
1508
+ * from the familiar location without needing a patch.
1522
1509
  *
1523
- * @remarks
1524
- * Splits the key path on `.` and walks the object tree. Returns `undefined`
1525
- * at the first missing or non-object segment.
1510
+ * This helper intentionally lives next to {@link ../harness/pi.ts} rather
1511
+ * than being exported at a higher level three-tier scope is a Pi
1512
+ * concept today, and other harnesses (if any ever land) will want their
1513
+ * own mapping.
1526
1514
  *
1527
- * @example
1528
- * ```typescript
1529
- * getNestedValue({ a: { b: { c: 42 } } }, "a.b.c"); // 42
1530
- * getNestedValue({ a: 1 }, "a.b"); // undefined
1531
- * ```
1515
+ * @see ADR-035 §D1 (Three-tier scope hierarchy with explicit precedence)
1532
1516
  *
1533
- * @public
1517
+ * @packageDocumentation
1534
1518
  */
1535
- declare function getNestedValue(obj: Record<string, unknown>, keyPath: string): unknown;
1536
1519
  /**
1537
- * Ensure that the parent directories of a file path exist.
1538
- *
1539
- * Creates directories recursively if they do not exist.
1540
- *
1541
- * @param filePath - Absolute path to a file (parent directories will be created)
1520
+ * Three-tier scope identifier for Pi harness operations.
1542
1521
  *
1543
1522
  * @remarks
1544
- * Uses `mkdir` with `recursive: true` so existing directories are not an error.
1545
- *
1546
- * @example
1547
- * ```typescript
1548
- * await ensureDir("/path/to/new/dir/file.json");
1549
- * // /path/to/new/dir/ now exists
1550
- * ```
1523
+ * Each tier maps to a distinct root directory; see the module overview
1524
+ * for the precedence and resolution rules. This is distinct from (and
1525
+ * coexists with) the legacy {@link HarnessScope} discriminated union,
1526
+ * which only supports two tiers (`global`/`project`) and is preserved
1527
+ * for back-compat with existing skill/instruction installers.
1551
1528
  *
1552
1529
  * @public
1553
1530
  */
1554
- declare function ensureDir(filePath: string): Promise<void>;
1531
+ type HarnessTier = 'project' | 'user' | 'global';
1555
1532
 
1556
1533
  /**
1557
- * Provides format-agnostic config read, write, and remove operations that
1558
- * dispatch to JSON/JSONC, YAML, or TOML handlers based on the specified
1559
- * format.
1534
+ * Harness layer type definitions.
1535
+ *
1536
+ * @remarks
1537
+ * Defines the contract every first-class CAAMP harness must implement.
1538
+ * A harness is a provider that CAAMP treats natively — not through the
1539
+ * generic MCP-config-file path. Pi is the first (and currently only)
1540
+ * harness. The interface is intentionally generic so future harnesses
1541
+ * (Goose, OpenCode, ...) can slot in without shape churn.
1542
+ *
1543
+ * All methods are async even when they could be sync, so implementations
1544
+ * that need I/O (filesystem, child process) are not forced to lie about
1545
+ * their return types.
1560
1546
  *
1561
1547
  * @packageDocumentation
1562
1548
  */
1563
1549
 
1564
1550
  /**
1565
- * Read and parse a config file in the specified format.
1566
- *
1567
- * Dispatches to the appropriate format handler (JSON/JSONC, YAML, or TOML).
1568
- *
1569
- * @param filePath - Absolute path to the config file
1570
- * @param format - Config file format
1571
- * @returns Parsed config object
1572
- * @throws If the file cannot be read or the format is unsupported
1551
+ * Scope at which a harness operation should be performed.
1573
1552
  *
1574
1553
  * @remarks
1575
- * Supported formats: `"json"`, `"jsonc"`, `"yaml"`, `"toml"`. Throws for
1576
- * any unrecognized format string.
1554
+ * Harness operations target either the user's global state root (e.g.
1555
+ * `~/.pi/agent/`) or a specific project directory. Project scope requires
1556
+ * the caller to provide the absolute project directory path — the harness
1557
+ * does not infer cwd.
1577
1558
  *
1578
- * @example
1579
- * ```typescript
1580
- * const config = await readConfig("/path/to/config.json", "jsonc");
1581
- * ```
1559
+ * This two-tier scope is the legacy shape used by the skill and
1560
+ * instructions install paths. Pi-specific Wave-1 verbs (extensions,
1561
+ * sessions, models, prompts, themes) use the three-tier
1562
+ * {@link HarnessTier} hierarchy introduced in ADR-035 §D1 alongside this
1563
+ * type — the two coexist and neither replaces the other.
1582
1564
  *
1583
1565
  * @public
1584
1566
  */
1585
- declare function readConfig(filePath: string, format: ConfigFormat): Promise<Record<string, unknown>>;
1567
+ type HarnessScope = {
1568
+ kind: 'global';
1569
+ } | {
1570
+ kind: 'project';
1571
+ projectDir: string;
1572
+ };
1586
1573
  /**
1587
- * Write a server entry to a config file, preserving existing content.
1588
- *
1589
- * Dispatches to the appropriate format handler. For JSONC files, comments are
1590
- * preserved using `jsonc-parser`.
1591
- *
1592
- * @param filePath - Absolute path to the config file
1593
- * @param format - Config file format
1594
- * @param key - Dot-notation key path to the servers section (e.g. `"mcpServers"`)
1595
- * @param serverName - Name/key for the server entry
1596
- * @param serverConfig - Server configuration object to write
1597
- * @throws If the format is unsupported
1574
+ * Options accepted by `resolveDefaultTargetProviders` (defined in
1575
+ * `./index.ts`).
1598
1576
  *
1599
1577
  * @remarks
1600
- * For JSONC files, comments and formatting are preserved using `jsonc-parser`.
1601
- * For YAML and TOML, the file is fully re-serialized after deep-merging.
1578
+ * Both fields are optional. When the entire options object is omitted the
1579
+ * helper behaves exactly as it did pre-ADR-035 §D7 in `auto` mode with Pi
1580
+ * installed — the no-regression contract for existing callers.
1602
1581
  *
1603
- * @example
1604
- * ```typescript
1605
- * await writeConfig("/path/to/config.json", "jsonc", "mcpServers", "my-server", config);
1606
- * ```
1582
+ * Lives here in `types.ts` rather than next to the function so that
1583
+ * downstream packages can `import type { ResolveDefaultTargetProvidersOptions }`
1584
+ * without pulling in the harness dispatcher's runtime imports.
1607
1585
  *
1608
1586
  * @public
1609
1587
  */
1610
- declare function writeConfig(filePath: string, format: ConfigFormat, key: string, serverName: string, serverConfig: unknown): Promise<void>;
1588
+ interface ResolveDefaultTargetProvidersOptions {
1589
+ /**
1590
+ * Explicit list of providers requested by the user (e.g. via `--agent`).
1591
+ *
1592
+ * @remarks
1593
+ * When supplied, this list signals that the caller is honouring an
1594
+ * explicit user selection rather than asking for the implicit default.
1595
+ * In `auto` mode, an explicit list that excludes Pi while Pi is installed
1596
+ * triggers a one-time deprecation warning per process. In `force-pi`
1597
+ * mode the explicit list is ignored if it does not contain Pi (Pi is
1598
+ * still required). In `legacy` mode the list is returned verbatim with
1599
+ * no warning.
1600
+ *
1601
+ * Pass `undefined` (or omit entirely) to request the implicit default
1602
+ * resolution.
1603
+ *
1604
+ * @defaultValue undefined
1605
+ */
1606
+ explicit?: Provider[];
1607
+ }
1611
1608
  /**
1612
- * Remove a server entry from a config file in the specified format.
1613
- *
1614
- * @param filePath - Absolute path to the config file
1615
- * @param format - Config file format
1616
- * @param key - Dot-notation key path to the servers section
1617
- * @param serverName - Name/key of the server entry to remove
1618
- * @returns `true` if the entry was removed, `false` otherwise
1619
- * @throws If the format is unsupported
1609
+ * Controls how `resolveDefaultTargetProviders` (defined in `./index.ts`)
1610
+ * selects target providers at runtime invocation time.
1620
1611
  *
1621
1612
  * @remarks
1622
- * Delegates to the format-specific removal function. Returns `false` when the
1623
- * file does not exist or the entry is not found.
1613
+ * Introduced by ADR-035 §D7 ("v3 exclusivity") to give users an explicit
1614
+ * knob over how aggressively CAAMP routes runtime commands through Pi
1615
+ * versus other installed providers.
1624
1616
  *
1625
- * @example
1626
- * ```typescript
1627
- * const removed = await removeConfig("/path/to/config.json", "jsonc", "mcpServers", "my-server");
1628
- * ```
1617
+ * - `'auto'` (default) — Pi is preferred when installed; explicit
1618
+ * `--agent <non-pi>` selections are still honoured but emit a one-time
1619
+ * deprecation warning per process. Behaviourally identical to v2026.4.5
1620
+ * when no explicit non-Pi target is supplied.
1621
+ * - `'force-pi'` — Pi is required at runtime invocation. The dispatcher
1622
+ * throws with an `E_NOT_FOUND_RESOURCE`-shaped error when Pi is not
1623
+ * installed; callers decide whether to surface this as an exit code 4
1624
+ * or render a remediation hint.
1625
+ * - `'legacy'` — Pre-exclusivity behaviour. No deprecation warnings, no
1626
+ * hard requirement on Pi. Matches the resolution order CAAMP shipped
1627
+ * before ADR-035 §D7 landed; preserved so users with brittle scripts
1628
+ * can pin until they migrate.
1629
+ *
1630
+ * **Important**: this setting affects RUNTIME INVOCATION paths only. Skill
1631
+ * and instruction install paths
1632
+ * (e.g. {@link dispatchInstallSkillAcrossProviders} and friends) are
1633
+ * UNAFFECTED and continue to dispatch to multiple providers regardless of
1634
+ * mode. See ADR-035 §D7 for the full design rationale.
1629
1635
  *
1630
1636
  * @public
1631
1637
  */
1632
- declare function removeConfig(filePath: string, format: ConfigFormat, key: string, serverName: string): Promise<boolean>;
1633
-
1638
+ type ExclusivityMode = 'auto' | 'force-pi' | 'legacy';
1634
1639
  /**
1635
- * Skill installer - canonical + symlink model
1640
+ * Metadata describing a Pi extension discovered on disk.
1636
1641
  *
1637
- * Skills are stored once in a canonical location (.agents/skills/<name>/)
1638
- * and symlinked to each target agent's skills directory.
1642
+ * @remarks
1643
+ * Returned by {@link Harness.listExtensions}. The `tier` records which
1644
+ * tier the extension lives at so that callers can surface the
1645
+ * precedence story in their output.
1646
+ *
1647
+ * @public
1639
1648
  */
1640
-
1649
+ interface ExtensionEntry {
1650
+ /** Extension name (file basename without the `.ts` extension). */
1651
+ name: string;
1652
+ /** Tier at which this entry lives. */
1653
+ tier: HarnessTier;
1654
+ /** Absolute on-disk path to the extension file. */
1655
+ path: string;
1656
+ /**
1657
+ * When `true`, this entry is shadowed by a higher-precedence entry
1658
+ * with the same name. Exposed so list output can warn about
1659
+ * cross-tier name collisions per ADR-035 §D1.
1660
+ * @defaultValue false
1661
+ */
1662
+ shadowed?: boolean;
1663
+ }
1641
1664
  /**
1642
- * Result of installing a skill to the canonical location and linking to agents.
1665
+ * Metadata describing a Pi prompt directory discovered on disk.
1643
1666
  *
1644
- * @example
1645
- * ```typescript
1646
- * const result = await installSkill(sourcePath, "my-skill", providers, true);
1647
- * if (result.success) {
1648
- * console.log(`Installed to ${result.canonicalPath}`);
1649
- * console.log(`Linked to: ${result.linkedAgents.join(", ")}`);
1650
- * }
1651
- * ```
1667
+ * @remarks
1668
+ * Returned by {@link Harness.listPrompts}. Prompts are directories
1669
+ * containing a `prompt.md` plus optional metadata. The list operation
1670
+ * reads only the directory listing — never the prompt bodies — to keep
1671
+ * token usage minimal per ADR-035 §D1.
1652
1672
  *
1653
1673
  * @public
1654
1674
  */
1655
- interface SkillInstallResult {
1656
- /** Skill name. */
1675
+ interface PromptEntry {
1676
+ /** Prompt name (directory basename). */
1657
1677
  name: string;
1658
- /** Absolute path to the canonical installation directory. */
1659
- canonicalPath: string;
1660
- /** Provider IDs that were successfully linked. */
1661
- linkedAgents: string[];
1662
- /** Error messages from failed link operations. */
1663
- errors: string[];
1664
- /** Whether at least one agent was successfully linked. */
1665
- success: boolean;
1678
+ /** Tier at which this entry lives. */
1679
+ tier: HarnessTier;
1680
+ /** Absolute on-disk path to the prompt directory. */
1681
+ path: string;
1682
+ /** See {@link ExtensionEntry.shadowed}. @defaultValue false */
1683
+ shadowed?: boolean;
1666
1684
  }
1667
1685
  /**
1668
- * Install a skill from a local path to the canonical location and link to agents.
1686
+ * Metadata describing a Pi theme discovered on disk.
1669
1687
  *
1670
1688
  * @remarks
1671
- * Copies the skill directory to the canonical skills directory and creates symlinks
1672
- * (or copies on Windows) from each provider's skills directory to the canonical path.
1673
- *
1674
- * @param sourcePath - Local path to the skill directory to install
1675
- * @param skillName - Name for the installed skill
1676
- * @param providers - Target providers to link the skill to
1677
- * @param isGlobal - Whether to link to global or project skill directories
1678
- * @param projectDir - Project directory (defaults to `process.cwd()`)
1679
- * @returns Install result with linked agents and any errors
1689
+ * Returned by {@link Harness.listThemes}. Themes are single `.ts` or
1690
+ * `.json` files matching Pi's native theme module shape.
1680
1691
  *
1681
- * @example
1682
- * ```typescript
1683
- * const result = await installSkill("/tmp/my-skill", "my-skill", providers, true, "/my/project");
1684
- * if (result.success) {
1685
- * console.log(`Linked to: ${result.linkedAgents.join(", ")}`);
1686
- * }
1687
- * ```
1692
+ * @public
1693
+ */
1694
+ interface ThemeEntry {
1695
+ /** Theme name (file basename without the extension). */
1696
+ name: string;
1697
+ /** Tier at which this entry lives. */
1698
+ tier: HarnessTier;
1699
+ /** Absolute on-disk path to the theme file. */
1700
+ path: string;
1701
+ /** File extension of the theme file (e.g. `".ts"`, `".json"`). */
1702
+ fileExt: string;
1703
+ /** See {@link ExtensionEntry.shadowed}. @defaultValue false */
1704
+ shadowed?: boolean;
1705
+ }
1706
+ /**
1707
+ * Options accepted by the Pi install verbs (extensions, prompts, themes).
1688
1708
  *
1689
1709
  * @public
1690
1710
  */
1691
- declare function installSkill(sourcePath: string, skillName: string, providers: Provider[], isGlobal: boolean, projectDir?: string): Promise<SkillInstallResult>;
1711
+ interface HarnessInstallOptions {
1712
+ /**
1713
+ * When `true`, overwrite an existing file at the target tier. When
1714
+ * `false` (the default) the install verb throws if the target exists.
1715
+ * @defaultValue false
1716
+ */
1717
+ force?: boolean;
1718
+ }
1692
1719
  /**
1693
- * Remove a skill from the canonical location and all agent symlinks.
1720
+ * Counts of top-level CANT sections discovered in a `.cant` file.
1694
1721
  *
1695
1722
  * @remarks
1696
- * Removes symlinks from each provider's skills directory and then removes the
1697
- * canonical copy from the centralized canonical skills directory.
1723
+ * Returned by {@link Harness.listCantProfiles} and
1724
+ * {@link Harness.validateCantProfile} so that callers can surface a
1725
+ * concise summary of every profile (agents, workflows, pipelines,
1726
+ * top-level hooks, and skill references declared by an agent's
1727
+ * `skills:` property) without reading the full document body.
1698
1728
  *
1699
- * @param skillName - Name of the skill to remove
1700
- * @param providers - Providers to unlink the skill from
1701
- * @param isGlobal - Whether to target global or project skill directories
1702
- * @param projectDir - Project directory (defaults to `process.cwd()`)
1703
- * @returns Object with arrays of successfully removed provider IDs and error messages
1704
- *
1705
- * @example
1706
- * ```typescript
1707
- * const { removed, errors } = await removeSkill("my-skill", providers, true, "/my/project");
1708
- * console.log(`Removed from: ${removed.join(", ")}`);
1709
- * ```
1729
+ * The cant-core AST tags every section as a single-key wrapper
1730
+ * (`{ Agent: { ... } }`, `{ Workflow: { ... } }`, etc.), so these
1731
+ * counters are derived by walking `document.sections` and bucketing
1732
+ * by tag. `skillCount` aggregates the unique skill names declared
1733
+ * across every Agent section's `skills:` property it is NOT a
1734
+ * count of standalone `skill { ... }` blocks because cant-core does
1735
+ * not currently expose those at the document level.
1710
1736
  *
1711
1737
  * @public
1712
1738
  */
1713
- declare function removeSkill(skillName: string, providers: Provider[], isGlobal: boolean, projectDir?: string): Promise<{
1714
- removed: string[];
1715
- errors: string[];
1716
- }>;
1739
+ interface CantProfileCounts {
1740
+ /** Number of `agent { ... }` sections in the document. */
1741
+ agentCount: number;
1742
+ /** Number of `workflow { ... }` sections in the document. */
1743
+ workflowCount: number;
1744
+ /** Number of `pipeline { ... }` sections in the document. */
1745
+ pipelineCount: number;
1746
+ /**
1747
+ * Number of hook bodies discovered. Top-level `Hook` sections plus
1748
+ * the `hooks` array nested inside every Agent section are summed.
1749
+ */
1750
+ hookCount: number;
1751
+ /**
1752
+ * Number of distinct skill names referenced via an Agent section's
1753
+ * `skills:` property (e.g. `skills: ["ct-cleo", "ct-task-executor"]`).
1754
+ * The count is de-duplicated across agents within a single document.
1755
+ */
1756
+ skillCount: number;
1757
+ }
1717
1758
  /**
1718
- * List all skills installed in the canonical skills directory.
1759
+ * Metadata describing a `.cant` profile installed at one of the three
1760
+ * Pi harness tiers.
1719
1761
  *
1720
1762
  * @remarks
1721
- * Returns the directory names of all skills, which correspond to skill names.
1722
- * Includes both regular directories and symlinks in the canonical location.
1723
- *
1724
- * @returns Array of skill names
1725
- *
1726
- * @example
1727
- * ```typescript
1728
- * const skills = await listCanonicalSkills();
1729
- * // ["my-skill", "another-skill"]
1730
- * ```
1763
+ * Returned by {@link Harness.listCantProfiles}. Mirrors the shape of
1764
+ * {@link ExtensionEntry} so consumers can render both with the same
1765
+ * UI primitives, but adds a {@link counts} bag with parsed AST stats
1766
+ * so the list output can summarise each profile without forcing the
1767
+ * caller to re-parse every file.
1731
1768
  *
1732
1769
  * @public
1733
1770
  */
1734
- declare function listCanonicalSkills(): Promise<string[]>;
1735
-
1771
+ interface CantProfileEntry {
1772
+ /** Profile name (basename of the `.cant` file without the extension). */
1773
+ name: string;
1774
+ /** Tier at which this entry lives. */
1775
+ tier: HarnessTier;
1776
+ /** Absolute on-disk path to the `.cant` file. */
1777
+ sourcePath: string;
1778
+ /** Parsed section counts for the profile. */
1779
+ counts: CantProfileCounts;
1780
+ /**
1781
+ * When `true`, this entry is shadowed by a higher-precedence entry
1782
+ * with the same name. Mirrors the shadow flag emitted by
1783
+ * {@link Harness.listExtensions} so the same UI logic applies.
1784
+ * @defaultValue false
1785
+ */
1786
+ shadowedByHigherTier?: boolean;
1787
+ }
1736
1788
  /**
1737
- * Harness layer type definitions.
1789
+ * Diagnostic emitted by the cant-core 42-rule validator, normalised to
1790
+ * the harness layer's vocabulary.
1738
1791
  *
1739
1792
  * @remarks
1740
- * Defines the contract every first-class CAAMP harness must implement.
1741
- * A harness is a provider that CAAMP treats natively not through the
1742
- * generic MCP-config-file path. Pi is the first (and currently only)
1743
- * harness. The interface is intentionally generic so future harnesses
1744
- * (Goose, OpenCode, ...) can slot in without shape churn.
1745
- *
1746
- * All methods are async even when they could be sync, so implementations
1747
- * that need I/O (filesystem, child process) are not forced to lie about
1748
- * their return types.
1793
+ * Mirrors the `NativeDiagnostic` shape exported by `@cleocode/cant`'s
1794
+ * native loader. Captured here as a first-class harness type so callers
1795
+ * never have to import from `@cleocode/cant` directly to consume
1796
+ * {@link Harness.validateCantProfile} results.
1749
1797
  *
1750
- * @packageDocumentation
1798
+ * @public
1751
1799
  */
1752
-
1800
+ interface CantValidationDiagnostic {
1801
+ /** Cant-core rule id (`PARSE`, `S01`, `P06`, ...). */
1802
+ ruleId: string;
1803
+ /** Human-readable diagnostic message. */
1804
+ message: string;
1805
+ /** 1-based line number where the diagnostic was emitted. */
1806
+ line: number;
1807
+ /** 1-based column number where the diagnostic was emitted. */
1808
+ col: number;
1809
+ /** Severity bucket from cant-core (`error`, `warning`, `info`, `hint`). */
1810
+ severity: 'error' | 'warning' | 'info' | 'hint';
1811
+ }
1753
1812
  /**
1754
- * Scope at which a harness operation should be performed.
1813
+ * Result of validating a `.cant` profile via the cant-core 42-rule
1814
+ * engine.
1755
1815
  *
1756
1816
  * @remarks
1757
- * Harness operations target either the user's global state root (e.g.
1758
- * `~/.pi/agent/`) or a specific project directory. Project scope requires
1759
- * the caller to provide the absolute project directory path — the harness
1760
- * does not infer cwd.
1817
+ * Returned by {@link Harness.validateCantProfile}. The `valid` flag is
1818
+ * `true` only when no error-severity diagnostics were emitted (warnings
1819
+ * are tolerated, matching cant-core's own definition). The
1820
+ * {@link counts} bag is populated only when parsing succeeded — when
1821
+ * the file is so broken cant-core cannot produce a document, every
1822
+ * counter is `0` and the diagnostics array carries the parse errors.
1761
1823
  *
1762
1824
  * @public
1763
1825
  */
1764
- type HarnessScope = {
1765
- kind: 'global';
1766
- } | {
1767
- kind: 'project';
1768
- projectDir: string;
1769
- };
1826
+ interface ValidateCantProfileResult {
1827
+ /** Whether validation passed (no error-severity diagnostics). */
1828
+ valid: boolean;
1829
+ /** All diagnostics emitted by the 42-rule engine, in source order. */
1830
+ errors: CantValidationDiagnostic[];
1831
+ /** Section counts for the profile (zero when parsing failed). */
1832
+ counts: CantProfileCounts;
1833
+ }
1834
+ /**
1835
+ * Summary header extracted from the first line of a Pi session JSONL file.
1836
+ *
1837
+ * @remarks
1838
+ * Per ADR-035 §D2, `list`-style session operations read only line 1 of
1839
+ * each `*.jsonl` file — never the full body — so this shape is what
1840
+ * callers consume when enumerating sessions. The full session loader
1841
+ * returns raw JSONL line strings as a separate type
1842
+ * ({@link SessionDocument}).
1843
+ *
1844
+ * @public
1845
+ */
1846
+ interface SessionSummary {
1847
+ /** Session identifier as recorded in the line-1 header. */
1848
+ id: string;
1849
+ /** Session version as recorded in the line-1 header (e.g. `3`). */
1850
+ version: number;
1851
+ /** ISO-8601 timestamp from the line-1 header, or `null` when absent. */
1852
+ timestamp: string | null;
1853
+ /** Working directory recorded when the session was created. */
1854
+ cwd: string | null;
1855
+ /** Parent session id, if this session was forked from another. */
1856
+ parentSession: string | null;
1857
+ /** Absolute path to the session JSONL file on disk. */
1858
+ filePath: string;
1859
+ /** File modification time in milliseconds since the epoch. */
1860
+ mtimeMs: number;
1861
+ }
1770
1862
  /**
1771
- * Declarative description of an MCP server that should be bridged into a
1772
- * harness's native extension mechanism.
1863
+ * Raw content of a Pi session JSONL file, preserved line-by-line.
1773
1864
  *
1774
1865
  * @remarks
1775
- * This is a harness-agnostic input shape. Fields mirror the MCP
1776
- * configuration surface: stdio transports use {@link command} + {@link args},
1777
- * remote transports use {@link url} + optional {@link headers}, and all
1778
- * transports may carry environment variables via {@link env}.
1866
+ * Returned by {@link Harness.showSession} when a caller needs the full
1867
+ * body. Each element is one JSONL line as a string (empty trailing
1868
+ * lines are stripped). Callers that need typed entries parse each line
1869
+ * themselves; the harness does not impose a type on entry bodies
1870
+ * because Pi's own entry schema is open-ended (`message`, `thinking`,
1871
+ * `custom`, etc.) and we do not want to fall behind Pi's schema
1872
+ * evolution.
1873
+ *
1874
+ * @public
1875
+ */
1876
+ interface SessionDocument {
1877
+ /** Header summary (same shape as {@link SessionSummary}). */
1878
+ summary: SessionSummary;
1879
+ /** Raw JSONL lines in file order, excluding the line-1 header. */
1880
+ entries: string[];
1881
+ }
1882
+ /**
1883
+ * Pi model definition as recorded under `models.json:providers[].models`.
1779
1884
  *
1780
- * Harnesses that cannot host MCP servers as extensions will throw or omit
1781
- * the {@link Harness.installMcpAsExtension} method entirely.
1885
+ * @remarks
1886
+ * Mirrors the `ModelDefinition` schema in
1887
+ * `@mariozechner/pi-coding-agent`'s model-registry. Fields are typed
1888
+ * loosely because Pi's schema is evolving (see ADR-035 §D3); the keys
1889
+ * captured here are the minimum a CAAMP verb needs to reason about.
1782
1890
  *
1783
1891
  * @public
1784
1892
  */
1785
- interface McpServerSpec {
1786
- /** Logical name of the MCP server (e.g. `"filesystem"`, `"brave-search"`). */
1893
+ interface PiModelDefinition {
1894
+ /** Model id within the provider (e.g. `"claude-opus-4-20250514"`). */
1895
+ id: string;
1896
+ /** Human-readable model name. */
1787
1897
  name: string;
1788
1898
  /**
1789
- * Command to launch the server over stdio transport.
1899
+ * Whether the model supports reasoning/thinking tokens.
1790
1900
  * @defaultValue undefined
1791
1901
  */
1792
- command?: string;
1902
+ reasoning?: boolean;
1793
1903
  /**
1794
- * Arguments for the stdio command.
1904
+ * Allowed input modalities (e.g. `["text"]`, `["text", "image"]`).
1795
1905
  * @defaultValue undefined
1796
1906
  */
1797
- args?: string[];
1907
+ input?: Array<'text' | 'image'>;
1798
1908
  /**
1799
- * URL for SSE/HTTP transports.
1909
+ * Context window size in tokens.
1800
1910
  * @defaultValue undefined
1801
1911
  */
1802
- url?: string;
1912
+ contextWindow?: number;
1803
1913
  /**
1804
- * Environment variables for the server process.
1914
+ * Maximum output tokens.
1805
1915
  * @defaultValue undefined
1806
1916
  */
1807
- env?: Record<string, string>;
1917
+ maxTokens?: number;
1918
+ }
1919
+ /**
1920
+ * Pi provider block as recorded under `models.json:providers[id]`.
1921
+ *
1922
+ * @remarks
1923
+ * Mirrors the `ProviderConfig` schema in Pi's model-registry.
1924
+ *
1925
+ * @public
1926
+ */
1927
+ interface PiModelProvider {
1808
1928
  /**
1809
- * HTTP headers for remote transports.
1929
+ * Custom base URL for the provider (overrides default).
1810
1930
  * @defaultValue undefined
1811
1931
  */
1812
- headers?: Record<string, string>;
1932
+ baseUrl?: string;
1933
+ /**
1934
+ * API key or `$ENV_VAR` reference.
1935
+ * @defaultValue undefined
1936
+ */
1937
+ apiKey?: string;
1938
+ /**
1939
+ * Custom model definitions declared by the user.
1940
+ * @defaultValue undefined
1941
+ */
1942
+ models?: PiModelDefinition[];
1943
+ }
1944
+ /**
1945
+ * Entire `models.json` document shape used by Pi.
1946
+ *
1947
+ * @remarks
1948
+ * Mirrors the `ModelsConfig` schema in Pi's model-registry. CAAMP reads
1949
+ * and writes this file through {@link Harness.readModelsConfig} /
1950
+ * {@link Harness.writeModelsConfig}.
1951
+ *
1952
+ * @public
1953
+ */
1954
+ interface PiModelsConfig {
1955
+ /** Map of provider id → provider block. */
1956
+ providers: Record<string, PiModelProvider>;
1957
+ }
1958
+ /**
1959
+ * A model entry as surfaced by {@link Harness.listModels}.
1960
+ *
1961
+ * @remarks
1962
+ * Models are reported as a union of `models.json`-defined custom models
1963
+ * and `settings.json:enabledModels` selections, with flags that record
1964
+ * whether each entry is currently enabled and whether it is the
1965
+ * configured default.
1966
+ *
1967
+ * @public
1968
+ */
1969
+ interface ModelListEntry {
1970
+ /** Provider id (e.g. `"anthropic"`). */
1971
+ provider: string;
1972
+ /** Model id within the provider. */
1973
+ id: string;
1974
+ /** Human-readable name, from `models.json` when available. */
1975
+ name: string | null;
1976
+ /** `true` when the model is present in `settings.json:enabledModels`. */
1977
+ enabled: boolean;
1978
+ /** `true` when the model is the configured default. */
1979
+ isDefault: boolean;
1980
+ /**
1981
+ * `true` when this model is defined in `models.json` (custom). When
1982
+ * `false`, the entry originates from `settings.json:enabledModels`
1983
+ * only and is assumed to resolve against Pi's built-in registry.
1984
+ */
1985
+ custom: boolean;
1813
1986
  }
1814
1987
  /**
1815
1988
  * Description of a subagent task to be spawned under a harness.
@@ -1820,6 +1993,12 @@ interface McpServerSpec {
1820
1993
  * is a routing hint passed to the harness; concrete harnesses may use it
1821
1994
  * to select an inner agent or simply record it for observability.
1822
1995
  *
1996
+ * Per ADR-035 §D6, every spawn has a stable {@link taskId} and is
1997
+ * attributed to a parent session via {@link parentSessionId}. When the
1998
+ * caller provides {@link parentSessionPath}, the harness records a
1999
+ * `subagent_link` custom entry into that file so listing the parent
2000
+ * session surfaces its children automatically.
2001
+ *
1823
2002
  * @public
1824
2003
  */
1825
2004
  interface SubagentTask {
@@ -1827,6 +2006,42 @@ interface SubagentTask {
1827
2006
  targetProviderId: string;
1828
2007
  /** The prompt / instruction to give the spawned agent. */
1829
2008
  prompt: string;
2009
+ /**
2010
+ * Stable task identifier used to derive the child session filename and
2011
+ * to correlate streamed events with their originating task.
2012
+ *
2013
+ * @remarks
2014
+ * When omitted, the harness generates a short id at spawn time so
2015
+ * legacy callers (pre-ADR-035 §D6) keep working. New callers SHOULD
2016
+ * always supply a deterministic value.
2017
+ *
2018
+ * @defaultValue undefined
2019
+ */
2020
+ taskId?: string;
2021
+ /**
2022
+ * Identifier of the parent session that owns this subagent.
2023
+ *
2024
+ * @remarks
2025
+ * Used to compose the child session filename
2026
+ * (`subagent-{parentSessionId}-{taskId}.jsonl`) per ADR-035 §D6.
2027
+ * When omitted, the harness substitutes `"orphan"` so legacy callers
2028
+ * still produce a well-formed file path.
2029
+ *
2030
+ * @defaultValue undefined
2031
+ */
2032
+ parentSessionId?: string;
2033
+ /**
2034
+ * Absolute path to the parent session JSONL file.
2035
+ *
2036
+ * @remarks
2037
+ * When supplied, the harness appends a {@link SubagentLinkEntry} as
2038
+ * a `custom` entry to this file at spawn time so listing the parent
2039
+ * surfaces its children automatically. When omitted, no link entry
2040
+ * is written and the parent session is not modified.
2041
+ *
2042
+ * @defaultValue undefined
2043
+ */
2044
+ parentSessionPath?: string;
1830
2045
  /**
1831
2046
  * Working directory for the spawned agent.
1832
2047
  * @defaultValue undefined
@@ -1838,18 +2053,136 @@ interface SubagentTask {
1838
2053
  */
1839
2054
  env?: Record<string, string>;
1840
2055
  /**
1841
- * Abort signal. When it aborts, the harness will terminate the subagent.
2056
+ * Abort signal. When it aborts, the harness will terminate the subagent
2057
+ * via the configured SIGTERM-then-SIGKILL cleanup sequence.
1842
2058
  * @defaultValue undefined
1843
2059
  */
1844
2060
  signal?: AbortSignal;
1845
2061
  }
1846
2062
  /**
1847
- * Final result of a subagent's execution.
2063
+ * Per-call options that override harness-wide spawn defaults.
1848
2064
  *
1849
2065
  * @remarks
1850
- * Collected once the child process exits. {@link parsed} is populated
1851
- * on a best-effort basis: if the subagent emits JSON on stdout the
1852
- * harness will parse it, otherwise {@link parsed} is left undefined.
2066
+ * Introduced for ADR-035 §D6 streaming + cleanup semantics. Every field
2067
+ * is optional so callers that just want default behaviour can omit the
2068
+ * second argument entirely.
2069
+ *
2070
+ * @public
2071
+ */
2072
+ interface SubagentSpawnOptions {
2073
+ /**
2074
+ * Streaming callback invoked once per parsed event from the child.
2075
+ *
2076
+ * @remarks
2077
+ * The harness fires this for every line of stdout (parsed as JSON when
2078
+ * possible), every line of stderr, the final exit, and the
2079
+ * `subagent_link` write. Callbacks are best-effort: throwing from the
2080
+ * callback is caught and recorded as a stderr line so the spawn loop
2081
+ * is never aborted by user code.
2082
+ *
2083
+ * @defaultValue undefined
2084
+ */
2085
+ onStream?: (event: SubagentStreamEvent) => void;
2086
+ /**
2087
+ * Override the SIGTERM grace window before SIGKILL fires.
2088
+ *
2089
+ * @remarks
2090
+ * When omitted, the harness reads
2091
+ * `settings.json:pi.subagent.terminateGraceMs` (global scope) and
2092
+ * falls back to `5000` ms if absent or invalid. Tests use very small
2093
+ * values to keep cleanup checks fast.
2094
+ *
2095
+ * @defaultValue undefined
2096
+ */
2097
+ terminateGraceMs?: number;
2098
+ /**
2099
+ * Environment variable overrides layered atop the task-level env.
2100
+ *
2101
+ * @remarks
2102
+ * Convenience hook for per-call secrets that should not live on the
2103
+ * task object itself. Merged after {@link SubagentTask.env} so
2104
+ * call-site keys win.
2105
+ *
2106
+ * @defaultValue undefined
2107
+ */
2108
+ env?: Record<string, string>;
2109
+ /**
2110
+ * Working directory override that wins over {@link SubagentTask.cwd}.
2111
+ *
2112
+ * @remarks
2113
+ * Useful when the same task description is reused across multiple
2114
+ * working directories.
2115
+ *
2116
+ * @defaultValue undefined
2117
+ */
2118
+ cwd?: string;
2119
+ }
2120
+ /**
2121
+ * One streaming event surfaced through {@link SubagentSpawnOptions.onStream}.
2122
+ *
2123
+ * @remarks
2124
+ * Discriminated by {@link kind}:
2125
+ *
2126
+ * - `"message"` — a successfully parsed JSON line from the child's
2127
+ * stdout. {@link payload} is the parsed object and {@link lineNumber}
2128
+ * is the 1-based line index within the child's stdout stream.
2129
+ * - `"stderr"` — a single line from the child's stderr stream. The
2130
+ * {@link payload} is `{ line: string }`. The harness NEVER injects
2131
+ * stderr into the parent LLM context per ADR-035 §D6.
2132
+ * - `"exit"` — the child has exited. {@link payload} is a
2133
+ * {@link SubagentExitResult}.
2134
+ * - `"link"` — the harness wrote a `subagent_link` custom entry to the
2135
+ * parent session. {@link payload} is the {@link SubagentLinkEntry}.
2136
+ *
2137
+ * @public
2138
+ */
2139
+ interface SubagentStreamEvent {
2140
+ /** Event kind discriminator. */
2141
+ kind: 'message' | 'stderr' | 'exit' | 'link';
2142
+ /** Subagent identifier (matches {@link SubagentHandle.subagentId}). */
2143
+ subagentId: string;
2144
+ /**
2145
+ * 1-based line number within the child's stdout stream. Only set for
2146
+ * `"message"` events that originated from a parsed stdout line.
2147
+ * @defaultValue undefined
2148
+ */
2149
+ lineNumber?: number;
2150
+ /** Event payload, shaped according to {@link kind}. */
2151
+ payload: unknown;
2152
+ }
2153
+ /**
2154
+ * Resolution value of {@link SubagentHandle.exitPromise}.
2155
+ *
2156
+ * @remarks
2157
+ * Captured exactly once when the child process exits. The promise NEVER
2158
+ * rejects — failure is encoded by a non-zero {@link code}, a non-null
2159
+ * {@link signal}, or partial output preserved in the child session file
2160
+ * at {@link childSessionPath}.
2161
+ *
2162
+ * @public
2163
+ */
2164
+ interface SubagentExitResult {
2165
+ /**
2166
+ * Process exit code, or `null` when the child was terminated by a
2167
+ * signal before exiting normally.
2168
+ */
2169
+ code: number | null;
2170
+ /** Terminating signal, or `null` when the child exited normally. */
2171
+ signal: NodeJS.Signals | null;
2172
+ /** Absolute path to the child session JSONL file on disk. */
2173
+ childSessionPath: string;
2174
+ /** Wall-clock duration from spawn to exit, in milliseconds. */
2175
+ durationMs: number;
2176
+ }
2177
+ /**
2178
+ * Final result of a subagent's execution (legacy v1 shape).
2179
+ *
2180
+ * @remarks
2181
+ * Preserved for back-compat with pre-ADR-035 §D6 callers. New code
2182
+ * SHOULD await {@link SubagentHandle.exitPromise} (which resolves with
2183
+ * a richer {@link SubagentExitResult}) instead. The harness still
2184
+ * populates this field on every spawn so existing tests and callers
2185
+ * keep working.
1853
2186
  *
1854
2187
  * @public
1855
2188
  */
@@ -1872,18 +2205,87 @@ interface SubagentResult {
1872
2205
  *
1873
2206
  * @remarks
1874
2207
  * Returned synchronously from {@link Harness.spawnSubagent}. The caller
1875
- * may await {@link result} to collect the final output, or invoke
1876
- * {@link abort} to terminate the child early.
2208
+ * may:
2209
+ *
2210
+ * - Await {@link exitPromise} to collect the rich {@link SubagentExitResult}
2211
+ * (preferred path, ADR-035 §D6).
2212
+ * - Await {@link result} to collect the legacy {@link SubagentResult}
2213
+ * (preserved for back-compat).
2214
+ * - Invoke {@link terminate} (preferred) or {@link abort} (legacy) to
2215
+ * stop the child early via the configured SIGTERM-then-SIGKILL
2216
+ * cleanup sequence.
2217
+ * - Inspect {@link recentStderr} for the most recent stderr lines
2218
+ * captured by the harness — useful for post-mortem diagnostics
2219
+ * without injecting stderr into the parent LLM context.
1877
2220
  *
1878
2221
  * @public
1879
2222
  */
1880
2223
  interface SubagentHandle {
2224
+ /**
2225
+ * Stable subagent identifier generated at spawn time.
2226
+ *
2227
+ * @remarks
2228
+ * Format: `sub-{taskId}-{shortRandom}`. Used to correlate
2229
+ * {@link SubagentStreamEvent} entries and `subagent_link` records
2230
+ * with this handle. Always defined (the harness never returns a
2231
+ * handle without one).
2232
+ */
2233
+ subagentId: string;
2234
+ /** Task identifier from {@link SubagentTask.taskId} (or generated default). */
2235
+ taskId: string;
2236
+ /** Absolute path to the child session JSONL file on disk. */
2237
+ childSessionPath: string;
1881
2238
  /** PID of the spawned process, or `null` if spawning did not yield one. */
1882
2239
  pid: number | null;
1883
- /** Promise resolving to the subagent's final output once the process exits. */
2240
+ /** Wall-clock timestamp captured immediately after spawn. */
2241
+ startedAt: Date;
2242
+ /**
2243
+ * Promise resolving to the rich exit result once the child process
2244
+ * has fully terminated. NEVER rejects — failures are encoded in the
2245
+ * resolved value (non-zero code, non-null signal, partial output in
2246
+ * the session file).
2247
+ */
2248
+ exitPromise: Promise<SubagentExitResult>;
2249
+ /**
2250
+ * Promise resolving to the legacy {@link SubagentResult} shape.
2251
+ *
2252
+ * @remarks
2253
+ * Preserved for back-compat. Resolves to the same exit code as
2254
+ * {@link exitPromise} plus the full captured stdout / stderr buffers
2255
+ * and a best-effort `parsed` field for callers that emit a single
2256
+ * JSON document.
2257
+ */
1884
2258
  result: Promise<SubagentResult>;
1885
- /** Synchronously terminate the subagent. Safe to call after exit. */
2259
+ /**
2260
+ * Terminate the subagent gracefully.
2261
+ *
2262
+ * @remarks
2263
+ * Sends SIGTERM, waits for the configured grace window, then sends
2264
+ * SIGKILL if the child is still alive. Idempotent — subsequent calls
2265
+ * after the first are no-ops. Returns once the cleanup sequence has
2266
+ * fully resolved.
2267
+ */
2268
+ terminate(): Promise<void>;
2269
+ /**
2270
+ * Synchronously trigger the cleanup sequence (legacy v1 alias for
2271
+ * {@link terminate}).
2272
+ *
2273
+ * @remarks
2274
+ * Preserved so existing callers that use `handle.abort()` keep
2275
+ * working. Internally enqueues the same SIGTERM-then-SIGKILL flow as
2276
+ * {@link terminate} but does not return the resulting promise.
2277
+ */
1886
2278
  abort: () => void;
2279
+ /**
2280
+ * Snapshot of the most recent stderr lines captured for this child.
2281
+ *
2282
+ * @remarks
2283
+ * Bounded ring buffer (last 100 lines) so memory cannot grow without
2284
+ * bound under chatty stderr. Stderr is NEVER injected into the
2285
+ * parent LLM context — this accessor is intended for diagnostics and
2286
+ * post-mortem inspection only.
2287
+ */
2288
+ recentStderr(): string[];
1887
2289
  }
1888
2290
  /**
1889
2291
  * Contract every first-class harness must implement.
@@ -1895,9 +2297,9 @@ interface SubagentHandle {
1895
2297
  * harness; the interface is shaped so future harnesses (Goose, OpenCode, ...)
1896
2298
  * can be added without changing any caller code.
1897
2299
  *
1898
- * Optional methods ({@link installMcpAsExtension}, {@link spawnSubagent},
1899
- * {@link configureModels}) may be omitted by harnesses that cannot support
1900
- * them. Callers MUST feature-check before invoking.
2300
+ * Optional methods ({@link spawnSubagent}, {@link configureModels}) may be
2301
+ * omitted by harnesses that cannot support them. Callers MUST feature-check
2302
+ * before invoking.
1901
2303
  *
1902
2304
  * @public
1903
2305
  */
@@ -1965,38 +2367,31 @@ interface Harness {
1965
2367
  * @param scope - Instruction file scope.
1966
2368
  */
1967
2369
  removeInstructions(scope: HarnessScope): Promise<void>;
1968
- /**
1969
- * Install an MCP server as a harness extension.
1970
- *
1971
- * @remarks
1972
- * For legacy providers with a native MCP config file, this is a
1973
- * passthrough. For Pi it generates a TypeScript extension file under
1974
- * `extensions/` that wraps the MCP server as a Pi tool via
1975
- * `pi.registerTool()`.
1976
- *
1977
- * Optional — harnesses that cannot host MCP bridges should omit this
1978
- * method. Callers MUST feature-check before invoking.
1979
- *
1980
- * @param server - Server spec to bridge.
1981
- * @param scope - Install scope.
1982
- */
1983
- installMcpAsExtension?(server: McpServerSpec, scope: HarnessScope): Promise<void>;
1984
2370
  /**
1985
2371
  * Spawn a subagent under this harness's control.
1986
2372
  *
1987
2373
  * @remarks
1988
2374
  * For Pi: invokes `child_process.spawn` with the provider's configured
1989
2375
  * `capabilities.spawn.spawnCommand`, appending the task prompt as a
1990
- * trailing positional argument. The returned handle lets the caller await
1991
- * completion or abort early.
2376
+ * trailing positional argument. The returned handle lets the caller
2377
+ * await completion ({@link SubagentHandle.exitPromise}), terminate the
2378
+ * child via the SIGTERM-then-SIGKILL cleanup sequence
2379
+ * ({@link SubagentHandle.terminate}), and inspect recent stderr for
2380
+ * post-mortem diagnostics ({@link SubagentHandle.recentStderr}).
2381
+ *
2382
+ * Per ADR-035 §D6, this is the **only** canonical subagent spawn path
2383
+ * in CLEO. New callers MUST go through the harness instead of calling
2384
+ * `child_process.spawn` directly so session attribution, streaming,
2385
+ * and cleanup remain uniform.
1992
2386
  *
1993
2387
  * Optional — harnesses that cannot spawn other agents should omit this
1994
2388
  * method. Callers MUST feature-check before invoking.
1995
2389
  *
1996
2390
  * @param task - Subagent task specification.
2391
+ * @param opts - Per-call streaming and cleanup overrides.
1997
2392
  * @returns A live subagent handle.
1998
2393
  */
1999
- spawnSubagent?(task: SubagentTask): Promise<SubagentHandle>;
2394
+ spawnSubagent?(task: SubagentTask, opts?: SubagentSpawnOptions): Promise<SubagentHandle>;
2000
2395
  /**
2001
2396
  * Configure which models are available in the harness's model picker.
2002
2397
  *
@@ -2031,7 +2426,636 @@ interface Harness {
2031
2426
  * @param scope - Settings scope.
2032
2427
  */
2033
2428
  writeSettings(patch: Record<string, unknown>, scope: HarnessScope): Promise<void>;
2429
+ /**
2430
+ * Install a Pi extension TypeScript file from a local source path into
2431
+ * the given tier.
2432
+ *
2433
+ * @remarks
2434
+ * Per ADR-035 §D1 and the spec hook for T263, install verbs:
2435
+ * - Validate that the source is a `.ts` file with an `export default`.
2436
+ * - Copy (not symlink) the file into the target tier's extensions dir.
2437
+ * - Error by default when the target already exists; the caller may
2438
+ * pass `opts.force = true` to enable overwrite.
2439
+ *
2440
+ * Optional on the interface because only first-class harnesses with a
2441
+ * native extension mechanism support this verb.
2442
+ *
2443
+ * @param sourcePath - Absolute path to the source `.ts` file on disk.
2444
+ * @param name - Extension name (used as the target file basename).
2445
+ * @param tier - Target tier (`project`/`user`/`global`).
2446
+ * @param projectDir - Project directory (required when `tier='project'`).
2447
+ * @param opts - Install options (see {@link HarnessInstallOptions}).
2448
+ */
2449
+ installExtension?(sourcePath: string, name: string, tier: HarnessTier, projectDir?: string, opts?: HarnessInstallOptions): Promise<{
2450
+ targetPath: string;
2451
+ tier: HarnessTier;
2452
+ }>;
2453
+ /**
2454
+ * Remove a Pi extension by name from the given tier.
2455
+ *
2456
+ * @remarks
2457
+ * Missing files are tolerated silently so the verb is usable as an
2458
+ * idempotent "ensure absent" operation.
2459
+ *
2460
+ * @param name - Extension name (basename without `.ts`).
2461
+ * @param tier - Target tier to remove from.
2462
+ * @param projectDir - Project directory (required when `tier='project'`).
2463
+ * @returns `true` when a file was removed, `false` when none existed.
2464
+ */
2465
+ removeExtension?(name: string, tier: HarnessTier, projectDir?: string): Promise<boolean>;
2466
+ /**
2467
+ * List Pi extensions across all tiers, precedence-ordered.
2468
+ *
2469
+ * @remarks
2470
+ * Entries are returned in precedence order (project → user → global).
2471
+ * Higher-precedence tiers shadow lower-precedence entries with the
2472
+ * same name; the returned {@link ExtensionEntry.shadowed} flag
2473
+ * indicates shadowed copies so the caller can surface cross-tier name
2474
+ * collisions per ADR-035 §D1.
2475
+ *
2476
+ * @param projectDir - Project directory for the `project` tier. When
2477
+ * omitted the `project` tier is skipped rather than failing.
2478
+ */
2479
+ listExtensions?(projectDir?: string): Promise<ExtensionEntry[]>;
2480
+ /**
2481
+ * List Pi sessions from the user-tier sessions directory.
2482
+ *
2483
+ * @remarks
2484
+ * Per ADR-035 §D2, MUST read only line 1 of each `*.jsonl` file. The
2485
+ * result is sorted by `mtimeMs` descending so the most recent
2486
+ * sessions appear first.
2487
+ *
2488
+ * @param opts - Options controlling which directories to scan.
2489
+ */
2490
+ listSessions?(opts?: {
2491
+ includeSubagents?: boolean;
2492
+ }): Promise<SessionSummary[]>;
2493
+ /**
2494
+ * Load a Pi session's full body by id.
2495
+ *
2496
+ * @remarks
2497
+ * Reads the entire file as-is. The caller is responsible for
2498
+ * formatting / filtering; the harness only guarantees that the
2499
+ * returned `entries` are the raw JSONL lines in file order.
2500
+ *
2501
+ * @param id - Session id as recorded in the line-1 header.
2502
+ */
2503
+ showSession?(id: string): Promise<SessionDocument>;
2504
+ /**
2505
+ * List every model known to Pi — both custom (`models.json`) and
2506
+ * enabled selections (`settings.json:enabledModels`).
2507
+ *
2508
+ * @remarks
2509
+ * Per ADR-035 §D3, this is a read-only union with per-entry flags.
2510
+ * Mutation verbs (`add`, `remove`, `enable`, `disable`, `default`)
2511
+ * are separate methods to preserve the dual-file authority model.
2512
+ *
2513
+ * @param scope - Legacy two-tier scope (global/project) that
2514
+ * determines which `models.json` and `settings.json` files to read.
2515
+ */
2516
+ listModels?(scope: HarnessScope): Promise<ModelListEntry[]>;
2517
+ /**
2518
+ * Read `models.json` for the given scope.
2519
+ *
2520
+ * @remarks
2521
+ * Missing files resolve to `{ providers: {} }`. Malformed JSON also
2522
+ * resolves to the empty config rather than throwing, matching
2523
+ * {@link Harness.readSettings}'s tolerant contract.
2524
+ */
2525
+ readModelsConfig?(scope: HarnessScope): Promise<PiModelsConfig>;
2526
+ /**
2527
+ * Write `models.json` for the given scope atomically.
2528
+ *
2529
+ * @remarks
2530
+ * The full config is written, not merged. Callers should read, patch,
2531
+ * then write. Uses an atomic tmp-then-rename sequence so a crash
2532
+ * mid-write cannot corrupt the file.
2533
+ */
2534
+ writeModelsConfig?(config: PiModelsConfig, scope: HarnessScope): Promise<void>;
2535
+ /**
2536
+ * Install a Pi prompt from a source directory into the given tier.
2537
+ *
2538
+ * @remarks
2539
+ * Per ADR-035 §D1 and the spec hook for T266, the source is a
2540
+ * directory containing `prompt.md` plus optional metadata. The
2541
+ * directory is copied recursively into the target tier. Conflict
2542
+ * handling mirrors {@link installExtension}.
2543
+ */
2544
+ installPrompt?(sourceDir: string, name: string, tier: HarnessTier, projectDir?: string, opts?: HarnessInstallOptions): Promise<{
2545
+ targetPath: string;
2546
+ tier: HarnessTier;
2547
+ }>;
2548
+ /** List Pi prompts across all tiers. */
2549
+ listPrompts?(projectDir?: string): Promise<PromptEntry[]>;
2550
+ /** Remove a Pi prompt by name from the given tier. */
2551
+ removePrompt?(name: string, tier: HarnessTier, projectDir?: string): Promise<boolean>;
2552
+ /**
2553
+ * Install a Pi theme from a source file into the given tier.
2554
+ *
2555
+ * @remarks
2556
+ * Per ADR-035 §D1 and the spec hook for T267. The source may be a
2557
+ * `.ts` TypeScript theme module or a `.json` theme file; the file
2558
+ * extension is preserved so Pi picks the right loader.
2559
+ */
2560
+ installTheme?(sourceFile: string, name: string, tier: HarnessTier, projectDir?: string, opts?: HarnessInstallOptions): Promise<{
2561
+ targetPath: string;
2562
+ tier: HarnessTier;
2563
+ }>;
2564
+ /** List Pi themes across all tiers. */
2565
+ listThemes?(projectDir?: string): Promise<ThemeEntry[]>;
2566
+ /** Remove a Pi theme by name from the given tier. */
2567
+ removeTheme?(name: string, tier: HarnessTier, projectDir?: string): Promise<boolean>;
2568
+ /**
2569
+ * Install a CANT profile (`.cant` file) into the given tier.
2570
+ *
2571
+ * @remarks
2572
+ * Per ADR-035 §D5 (CANT single engine), CANT profiles are first-class
2573
+ * Pi assets consumed by the canonical `cant-bridge.ts` extension via
2574
+ * `/cant:load <file>` at runtime. This verb copies a source `.cant`
2575
+ * file into the requested tier so the bridge can discover it.
2576
+ *
2577
+ * Implementations MUST validate the source via cant-core's 42-rule
2578
+ * engine before copying — invalid `.cant` files are rejected. The
2579
+ * conflict-on-write rule from §D1 cross-cutting concerns applies:
2580
+ * existing targets cause an error unless `opts.force` is set.
2581
+ *
2582
+ * Optional on the interface because only first-class harnesses with a
2583
+ * native CANT bridge support this verb.
2584
+ *
2585
+ * @param sourcePath - Absolute path to the source `.cant` file on disk.
2586
+ * @param name - Profile name (used as the target file basename).
2587
+ * @param tier - Target tier (`project`/`user`/`global`).
2588
+ * @param projectDir - Project directory (required when `tier='project'`).
2589
+ * @param opts - Install options (see {@link HarnessInstallOptions}).
2590
+ */
2591
+ installCantProfile?(sourcePath: string, name: string, tier: HarnessTier, projectDir?: string, opts?: HarnessInstallOptions): Promise<{
2592
+ targetPath: string;
2593
+ tier: HarnessTier;
2594
+ counts: CantProfileCounts;
2595
+ }>;
2596
+ /**
2597
+ * Remove a CANT profile by name from the given tier.
2598
+ *
2599
+ * @remarks
2600
+ * Missing files are tolerated silently so the verb is usable as an
2601
+ * idempotent "ensure absent" operation.
2602
+ *
2603
+ * @param name - Profile name (basename without `.cant`).
2604
+ * @param tier - Target tier to remove from.
2605
+ * @param projectDir - Project directory (required when `tier='project'`).
2606
+ * @returns `true` when a file was removed, `false` when none existed.
2607
+ */
2608
+ removeCantProfile?(name: string, tier: HarnessTier, projectDir?: string): Promise<boolean>;
2609
+ /**
2610
+ * List CANT profiles across all tiers, precedence-ordered.
2611
+ *
2612
+ * @remarks
2613
+ * Walks every tier in {@link HarnessTier} precedence order
2614
+ * (project → user → global) and parses each `.cant` file to extract
2615
+ * its section counts. Higher-precedence tiers shadow lower-precedence
2616
+ * entries with the same name; the {@link CantProfileEntry.shadowedByHigherTier}
2617
+ * flag indicates shadowed copies so callers can warn about
2618
+ * cross-tier name collisions per ADR-035 §D1.
2619
+ *
2620
+ * @param projectDir - Project directory for the `project` tier. When
2621
+ * omitted the `project` tier is skipped rather than failing.
2622
+ */
2623
+ listCantProfiles?(projectDir?: string): Promise<CantProfileEntry[]>;
2624
+ /**
2625
+ * Validate a `.cant` file via cant-core's 42-rule engine without
2626
+ * installing anything.
2627
+ *
2628
+ * @remarks
2629
+ * Pure helper used by `caamp pi cant validate <path>`. Reads the file,
2630
+ * parses it, runs the validator, and returns a structured
2631
+ * {@link ValidateCantProfileResult}. Never mutates state on disk.
2632
+ *
2633
+ * @param sourcePath - Absolute path to the `.cant` file on disk.
2634
+ */
2635
+ validateCantProfile?(sourcePath: string): Promise<ValidateCantProfileResult>;
2636
+ }
2637
+
2638
+ /**
2639
+ * CAAMP-wide configuration accessors.
2640
+ *
2641
+ * @remarks
2642
+ * This module is the single source of truth for CAAMP configuration values
2643
+ * that affect runtime behaviour across the package. Today it carries one
2644
+ * setting — {@link ExclusivityMode} — introduced by ADR-035 §D7 to control
2645
+ * how `resolveDefaultTargetProviders()` selects target providers at runtime
2646
+ * invocation time.
2647
+ *
2648
+ * The accessor pattern intentionally uses a layered resolution order so the
2649
+ * setting can be overridden by tests, by environment variables in CI, and by
2650
+ * future programmatic callers (e.g. a `caamp config set` subcommand) without
2651
+ * any of those layers needing to know about the others:
2652
+ *
2653
+ * 1. Programmatic override via {@link setExclusivityMode} (highest priority).
2654
+ * 2. Environment variable `CAAMP_EXCLUSIVITY_MODE`.
2655
+ * 3. Default value `'auto'`.
2656
+ *
2657
+ * The programmatic override exists primarily for tests and for future
2658
+ * `caamp config set` integration; production code should prefer the
2659
+ * environment variable when wiring CI or shell sessions.
2660
+ *
2661
+ * @packageDocumentation
2662
+ */
2663
+
2664
+ /**
2665
+ * Default exclusivity mode used when no override is configured.
2666
+ *
2667
+ * @remarks
2668
+ * `auto` mirrors the v2026.4.5+ behaviour: Pi is preferred when installed,
2669
+ * but explicit non-Pi targets remain functional. This is the value the
2670
+ * accessor returns when neither {@link setExclusivityMode} nor the
2671
+ * `CAAMP_EXCLUSIVITY_MODE` environment variable is set.
2672
+ *
2673
+ * @public
2674
+ */
2675
+ declare const DEFAULT_EXCLUSIVITY_MODE: ExclusivityMode;
2676
+ /**
2677
+ * Environment variable name read by {@link getExclusivityMode} when no
2678
+ * programmatic override is active.
2679
+ *
2680
+ * @remarks
2681
+ * Exported as a constant so tests and downstream tooling can refer to the
2682
+ * canonical name without typo risk. The variable accepts the same three
2683
+ * literal values as the {@link ExclusivityMode} type.
2684
+ *
2685
+ * @public
2686
+ */
2687
+ declare const EXCLUSIVITY_MODE_ENV_VAR = "CAAMP_EXCLUSIVITY_MODE";
2688
+ /**
2689
+ * Error raised when {@link getExclusivityMode} resolves to `'force-pi'` but
2690
+ * Pi is not installed at the moment a runtime dispatch is requested.
2691
+ *
2692
+ * @remarks
2693
+ * Carries an `E_NOT_FOUND_RESOURCE`-shaped `code` so command-layer error
2694
+ * envelopes (LAFS) can map it to a stable category. Callers decide whether
2695
+ * to surface this as a process exit (typically code 4) or as a structured
2696
+ * envelope; the harness layer never calls `process.exit` itself.
2697
+ *
2698
+ * Lives next to the configuration accessor rather than in the harness
2699
+ * dispatcher so consumers that import the mode also pick up the matching
2700
+ * error type without a second module hop.
2701
+ *
2702
+ * @example
2703
+ * ```typescript
2704
+ * try {
2705
+ * const targets = resolveDefaultTargetProviders();
2706
+ * } catch (err) {
2707
+ * if (err instanceof PiRequiredError) {
2708
+ * process.exit(4);
2709
+ * }
2710
+ * throw err;
2711
+ * }
2712
+ * ```
2713
+ *
2714
+ * @public
2715
+ */
2716
+ declare class PiRequiredError extends Error {
2717
+ /** LAFS-stable error code identifying this failure mode. */
2718
+ readonly code: "E_NOT_FOUND_RESOURCE";
2719
+ /**
2720
+ * Construct a new {@link PiRequiredError}.
2721
+ *
2722
+ * @param message - Human-readable failure description; defaults to a
2723
+ * stable string suitable for direct CLI display.
2724
+ */
2725
+ constructor(message?: string);
2034
2726
  }
2727
+ /**
2728
+ * Type guard that narrows an arbitrary string to {@link ExclusivityMode}.
2729
+ *
2730
+ * @remarks
2731
+ * Used both internally and by callers that need to validate untrusted input
2732
+ * (e.g. CLI flag parsers, env var readers) before passing it through to
2733
+ * {@link setExclusivityMode}.
2734
+ *
2735
+ * @param value - Candidate value to validate.
2736
+ * @returns `true` when `value` is one of `'auto'`, `'force-pi'`, `'legacy'`.
2737
+ *
2738
+ * @example
2739
+ * ```typescript
2740
+ * if (isExclusivityMode(userInput)) {
2741
+ * setExclusivityMode(userInput);
2742
+ * }
2743
+ * ```
2744
+ *
2745
+ * @public
2746
+ */
2747
+ declare function isExclusivityMode(value: string): value is ExclusivityMode;
2748
+ /**
2749
+ * Resolve the active CAAMP exclusivity mode using the layered precedence
2750
+ * documented in {@link DEFAULT_EXCLUSIVITY_MODE}.
2751
+ *
2752
+ * @remarks
2753
+ * Resolution order:
2754
+ *
2755
+ * 1. Programmatic override (set via {@link setExclusivityMode}).
2756
+ * 2. Environment variable `CAAMP_EXCLUSIVITY_MODE`.
2757
+ * 3. {@link DEFAULT_EXCLUSIVITY_MODE} (`'auto'`).
2758
+ *
2759
+ * Invalid environment variable values are silently ignored (resolution
2760
+ * falls through to the default) so a typo in CI does not crash the CLI.
2761
+ * The value is never cached — every call re-reads the environment so tests
2762
+ * that mutate `process.env` see consistent results without needing to
2763
+ * reset module state.
2764
+ *
2765
+ * @returns The currently effective exclusivity mode.
2766
+ *
2767
+ * @example
2768
+ * ```typescript
2769
+ * const mode = getExclusivityMode();
2770
+ * if (mode === 'force-pi') {
2771
+ * // ...
2772
+ * }
2773
+ * ```
2774
+ *
2775
+ * @public
2776
+ */
2777
+ declare function getExclusivityMode(): ExclusivityMode;
2778
+ /**
2779
+ * Install a programmatic override for the exclusivity mode.
2780
+ *
2781
+ * @remarks
2782
+ * The override takes precedence over the environment variable for the
2783
+ * remainder of the process or until {@link resetExclusivityModeOverride}
2784
+ * is called. Intended for tests, for future `caamp config set` wiring, and
2785
+ * for short-lived runtime adjustments (e.g. a one-shot CLI flag).
2786
+ *
2787
+ * @param mode - Mode to install.
2788
+ *
2789
+ * @example
2790
+ * ```typescript
2791
+ * setExclusivityMode('force-pi');
2792
+ * try {
2793
+ * await runCommand();
2794
+ * } finally {
2795
+ * resetExclusivityModeOverride();
2796
+ * }
2797
+ * ```
2798
+ *
2799
+ * @public
2800
+ */
2801
+ declare function setExclusivityMode(mode: ExclusivityMode): void;
2802
+ /**
2803
+ * Clear any programmatic override installed by {@link setExclusivityMode}.
2804
+ *
2805
+ * @remarks
2806
+ * After calling this, {@link getExclusivityMode} resumes reading from the
2807
+ * environment variable (and falls back to the default when the env var is
2808
+ * unset or invalid). Idempotent — safe to call when no override is active.
2809
+ *
2810
+ * @public
2811
+ */
2812
+ declare function resetExclusivityModeOverride(): void;
2813
+
2814
+ /**
2815
+ * Format utility functions
2816
+ */
2817
+ /**
2818
+ * Deep merge two objects, with `source` values winning on conflict.
2819
+ *
2820
+ * Recursively merges nested plain objects. Arrays and non-object values from
2821
+ * `source` overwrite `target` values.
2822
+ *
2823
+ * @param target - Base object to merge into
2824
+ * @param source - Object with values that take precedence
2825
+ * @returns A new merged object (does not mutate inputs)
2826
+ *
2827
+ * @remarks
2828
+ * Only plain objects are recursively merged. Arrays and primitive values from
2829
+ * `source` replace `target` values outright. Neither input is mutated.
2830
+ *
2831
+ * @example
2832
+ * ```typescript
2833
+ * const merged = deepMerge({ a: 1, b: { c: 2 } }, { b: { d: 3 } });
2834
+ * // { a: 1, b: { c: 2, d: 3 } }
2835
+ * ```
2836
+ *
2837
+ * @public
2838
+ */
2839
+ declare function deepMerge(target: Record<string, unknown>, source: Record<string, unknown>): Record<string, unknown>;
2840
+ /**
2841
+ * Get a nested value from an object using a dot-notation key path.
2842
+ *
2843
+ * @param obj - Object to traverse
2844
+ * @param keyPath - Dot-separated key path (e.g. `"mcpServers"` or `"a.b.c"`)
2845
+ * @returns The value at the key path, or `undefined` if not found
2846
+ *
2847
+ * @remarks
2848
+ * Splits the key path on `.` and walks the object tree. Returns `undefined`
2849
+ * at the first missing or non-object segment.
2850
+ *
2851
+ * @example
2852
+ * ```typescript
2853
+ * getNestedValue({ a: { b: { c: 42 } } }, "a.b.c"); // 42
2854
+ * getNestedValue({ a: 1 }, "a.b"); // undefined
2855
+ * ```
2856
+ *
2857
+ * @public
2858
+ */
2859
+ declare function getNestedValue(obj: Record<string, unknown>, keyPath: string): unknown;
2860
+ /**
2861
+ * Ensure that the parent directories of a file path exist.
2862
+ *
2863
+ * Creates directories recursively if they do not exist.
2864
+ *
2865
+ * @param filePath - Absolute path to a file (parent directories will be created)
2866
+ *
2867
+ * @remarks
2868
+ * Uses `mkdir` with `recursive: true` so existing directories are not an error.
2869
+ *
2870
+ * @example
2871
+ * ```typescript
2872
+ * await ensureDir("/path/to/new/dir/file.json");
2873
+ * // /path/to/new/dir/ now exists
2874
+ * ```
2875
+ *
2876
+ * @public
2877
+ */
2878
+ declare function ensureDir(filePath: string): Promise<void>;
2879
+
2880
+ /**
2881
+ * Provides format-agnostic config read, write, and remove operations that
2882
+ * dispatch to JSON/JSONC, YAML, or TOML handlers based on the specified
2883
+ * format.
2884
+ *
2885
+ * @packageDocumentation
2886
+ */
2887
+
2888
+ /**
2889
+ * Read and parse a config file in the specified format.
2890
+ *
2891
+ * Dispatches to the appropriate format handler (JSON/JSONC, YAML, or TOML).
2892
+ *
2893
+ * @param filePath - Absolute path to the config file
2894
+ * @param format - Config file format
2895
+ * @returns Parsed config object
2896
+ * @throws If the file cannot be read or the format is unsupported
2897
+ *
2898
+ * @remarks
2899
+ * Supported formats: `"json"`, `"jsonc"`, `"yaml"`, `"toml"`. Throws for
2900
+ * any unrecognized format string.
2901
+ *
2902
+ * @example
2903
+ * ```typescript
2904
+ * const config = await readConfig("/path/to/config.json", "jsonc");
2905
+ * ```
2906
+ *
2907
+ * @public
2908
+ */
2909
+ declare function readConfig(filePath: string, format: ConfigFormat): Promise<Record<string, unknown>>;
2910
+ /**
2911
+ * Write a server entry to a config file, preserving existing content.
2912
+ *
2913
+ * Dispatches to the appropriate format handler. For JSONC files, comments are
2914
+ * preserved using `jsonc-parser`.
2915
+ *
2916
+ * @param filePath - Absolute path to the config file
2917
+ * @param format - Config file format
2918
+ * @param key - Dot-notation key path to the servers section (e.g. `"mcpServers"`)
2919
+ * @param serverName - Name/key for the server entry
2920
+ * @param serverConfig - Server configuration object to write
2921
+ * @throws If the format is unsupported
2922
+ *
2923
+ * @remarks
2924
+ * For JSONC files, comments and formatting are preserved using `jsonc-parser`.
2925
+ * For YAML and TOML, the file is fully re-serialized after deep-merging.
2926
+ *
2927
+ * @example
2928
+ * ```typescript
2929
+ * await writeConfig("/path/to/config.json", "jsonc", "mcpServers", "my-server", config);
2930
+ * ```
2931
+ *
2932
+ * @public
2933
+ */
2934
+ declare function writeConfig(filePath: string, format: ConfigFormat, key: string, serverName: string, serverConfig: unknown): Promise<void>;
2935
+ /**
2936
+ * Remove a server entry from a config file in the specified format.
2937
+ *
2938
+ * @param filePath - Absolute path to the config file
2939
+ * @param format - Config file format
2940
+ * @param key - Dot-notation key path to the servers section
2941
+ * @param serverName - Name/key of the server entry to remove
2942
+ * @returns `true` if the entry was removed, `false` otherwise
2943
+ * @throws If the format is unsupported
2944
+ *
2945
+ * @remarks
2946
+ * Delegates to the format-specific removal function. Returns `false` when the
2947
+ * file does not exist or the entry is not found.
2948
+ *
2949
+ * @example
2950
+ * ```typescript
2951
+ * const removed = await removeConfig("/path/to/config.json", "jsonc", "mcpServers", "my-server");
2952
+ * ```
2953
+ *
2954
+ * @public
2955
+ */
2956
+ declare function removeConfig(filePath: string, format: ConfigFormat, key: string, serverName: string): Promise<boolean>;
2957
+
2958
+ /**
2959
+ * Skill installer - canonical + symlink model
2960
+ *
2961
+ * Skills are stored once in a canonical location (.agents/skills/<name>/)
2962
+ * and symlinked to each target agent's skills directory.
2963
+ */
2964
+
2965
+ /**
2966
+ * Result of installing a skill to the canonical location and linking to agents.
2967
+ *
2968
+ * @example
2969
+ * ```typescript
2970
+ * const result = await installSkill(sourcePath, "my-skill", providers, true);
2971
+ * if (result.success) {
2972
+ * console.log(`Installed to ${result.canonicalPath}`);
2973
+ * console.log(`Linked to: ${result.linkedAgents.join(", ")}`);
2974
+ * }
2975
+ * ```
2976
+ *
2977
+ * @public
2978
+ */
2979
+ interface SkillInstallResult {
2980
+ /** Skill name. */
2981
+ name: string;
2982
+ /** Absolute path to the canonical installation directory. */
2983
+ canonicalPath: string;
2984
+ /** Provider IDs that were successfully linked. */
2985
+ linkedAgents: string[];
2986
+ /** Error messages from failed link operations. */
2987
+ errors: string[];
2988
+ /** Whether at least one agent was successfully linked. */
2989
+ success: boolean;
2990
+ }
2991
+ /**
2992
+ * Install a skill from a local path to the canonical location and link to agents.
2993
+ *
2994
+ * @remarks
2995
+ * Copies the skill directory to the canonical skills directory and creates symlinks
2996
+ * (or copies on Windows) from each provider's skills directory to the canonical path.
2997
+ *
2998
+ * @param sourcePath - Local path to the skill directory to install
2999
+ * @param skillName - Name for the installed skill
3000
+ * @param providers - Target providers to link the skill to
3001
+ * @param isGlobal - Whether to link to global or project skill directories
3002
+ * @param projectDir - Project directory (defaults to `process.cwd()`)
3003
+ * @returns Install result with linked agents and any errors
3004
+ *
3005
+ * @example
3006
+ * ```typescript
3007
+ * const result = await installSkill("/tmp/my-skill", "my-skill", providers, true, "/my/project");
3008
+ * if (result.success) {
3009
+ * console.log(`Linked to: ${result.linkedAgents.join(", ")}`);
3010
+ * }
3011
+ * ```
3012
+ *
3013
+ * @public
3014
+ */
3015
+ declare function installSkill(sourcePath: string, skillName: string, providers: Provider[], isGlobal: boolean, projectDir?: string): Promise<SkillInstallResult>;
3016
+ /**
3017
+ * Remove a skill from the canonical location and all agent symlinks.
3018
+ *
3019
+ * @remarks
3020
+ * Removes symlinks from each provider's skills directory and then removes the
3021
+ * canonical copy from the centralized canonical skills directory.
3022
+ *
3023
+ * @param skillName - Name of the skill to remove
3024
+ * @param providers - Providers to unlink the skill from
3025
+ * @param isGlobal - Whether to target global or project skill directories
3026
+ * @param projectDir - Project directory (defaults to `process.cwd()`)
3027
+ * @returns Object with arrays of successfully removed provider IDs and error messages
3028
+ *
3029
+ * @example
3030
+ * ```typescript
3031
+ * const { removed, errors } = await removeSkill("my-skill", providers, true, "/my/project");
3032
+ * console.log(`Removed from: ${removed.join(", ")}`);
3033
+ * ```
3034
+ *
3035
+ * @public
3036
+ */
3037
+ declare function removeSkill(skillName: string, providers: Provider[], isGlobal: boolean, projectDir?: string): Promise<{
3038
+ removed: string[];
3039
+ errors: string[];
3040
+ }>;
3041
+ /**
3042
+ * List all skills installed in the canonical skills directory.
3043
+ *
3044
+ * @remarks
3045
+ * Returns the directory names of all skills, which correspond to skill names.
3046
+ * Includes both regular directories and symlinks in the canonical location.
3047
+ *
3048
+ * @returns Array of skill names
3049
+ *
3050
+ * @example
3051
+ * ```typescript
3052
+ * const skills = await listCanonicalSkills();
3053
+ * // ["my-skill", "another-skill"]
3054
+ * ```
3055
+ *
3056
+ * @public
3057
+ */
3058
+ declare function listCanonicalSkills(): Promise<string[]>;
2035
3059
 
2036
3060
  /**
2037
3061
  * Pi coding agent harness.
@@ -2085,10 +3109,6 @@ declare class PiHarness implements Harness {
2085
3109
  * Resolve the skills directory for a given scope.
2086
3110
  */
2087
3111
  private skillsDir;
2088
- /**
2089
- * Resolve the extensions directory for a given scope.
2090
- */
2091
- private extensionsDir;
2092
3112
  /**
2093
3113
  * Resolve the settings.json path for a given scope.
2094
3114
  */
@@ -2113,38 +3133,219 @@ declare class PiHarness implements Harness {
2113
3133
  /** {@inheritDoc Harness.removeInstructions} */
2114
3134
  removeInstructions(scope: HarnessScope): Promise<void>;
2115
3135
  /**
2116
- * {@inheritDoc Harness.installMcpAsExtension}
3136
+ * Spawn a subagent through Pi's configured `spawnCommand` and return a
3137
+ * live handle bound to the canonical streaming, attribution, and
3138
+ * cleanup contract.
3139
+ *
3140
+ * @remarks
3141
+ * Per ADR-035 §D6 this is the **only** sanctioned subagent spawn path
3142
+ * in CLEO. All historical direct `child_process.spawn` callers in
3143
+ * subagent contexts (including the `cant-bridge.ts` Pi extension and
3144
+ * the legacy CLEO orchestrator paths) MUST migrate to this method so
3145
+ * the contract below holds uniformly. A custom biome rule banning
3146
+ * raw `spawn()` from subagent code is planned for v3 cleanup but is
3147
+ * intentionally NOT enforced in v2 to keep the migration incremental.
3148
+ *
3149
+ * **Streaming semantics** — Pi's `--mode json` produces line-delimited
3150
+ * JSON on stdout. The harness:
3151
+ *
3152
+ * - Line-buffers stdout, parses each line as JSON, and forwards a
3153
+ * `{ kind: 'message', subagentId, lineNumber, payload }`
3154
+ * {@link SubagentStreamEvent} via {@link SubagentSpawnOptions.onStream}.
3155
+ * Non-parseable lines increment a warning counter (recorded in the
3156
+ * child session as `{ type: 'raw' }`) but never crash the loop.
3157
+ * - Line-buffers stderr separately, forwards each line as
3158
+ * `{ kind: 'stderr', subagentId, payload: { line } }`, and stores
3159
+ * it in a 100-line ring buffer accessible via
3160
+ * {@link SubagentHandle.recentStderr}. Stderr is **never** injected
3161
+ * into the parent LLM context per ADR-035 §D6.
3162
+ * - Emits a final `{ kind: 'exit', subagentId, payload: SubagentExitResult }`
3163
+ * when the child terminates.
3164
+ *
3165
+ * **Session attribution** — Every spawn produces a child session JSONL
3166
+ * file at
3167
+ * `~/.pi/agent/sessions/subagents/subagent-{parentSessionId}-{taskId}.jsonl`.
3168
+ * The header line records the subagentId, taskId, and parent linkage.
3169
+ * When {@link SubagentTask.parentSessionPath} is supplied, a
3170
+ * {@link SubagentLinkEntry} is appended to the parent session file as
3171
+ * a JSONL line so listing the parent surfaces its children.
3172
+ *
3173
+ * **Exit propagation** — {@link SubagentHandle.exitPromise} resolves
3174
+ * with `{ code, signal, childSessionPath, durationMs }` exactly once
3175
+ * when the child exits. The promise NEVER rejects: failure is
3176
+ * encoded by a non-zero `code`, a non-null `signal`, or partial
3177
+ * output preserved in the child session file.
3178
+ *
3179
+ * **Cleanup** — {@link SubagentHandle.terminate} sends SIGTERM, waits
3180
+ * the configured grace window, then sends SIGKILL if the child is
3181
+ * still alive. The grace window is sourced from
3182
+ * {@link SubagentSpawnOptions.terminateGraceMs} when supplied,
3183
+ * otherwise from `settings.json:pi.subagent.terminateGraceMs`,
3184
+ * otherwise from {@link DEFAULT_TERMINATE_GRACE_MS}. A
3185
+ * `subagent_exit` entry with reason `terminated` is appended to the
3186
+ * child session file when cleanup runs.
3187
+ *
3188
+ * **Concurrency** — Use the static helpers
3189
+ * {@link PiHarness.raceSubagents} and
3190
+ * {@link PiHarness.settleAllSubagents} to compose `parallel: race`
3191
+ * and `parallel: settle` constructs from CANT workflows over multiple
3192
+ * handles.
3193
+ *
3194
+ * **Orphan handling** — On the first spawn the harness registers a
3195
+ * process-wide `'exit'` handler that terminates every still-active
3196
+ * subagent so a parent crash never strands children.
3197
+ *
3198
+ * Throws immediately when the provider entry is missing a
3199
+ * `spawnCommand` so callers see configuration errors early rather
3200
+ * than at child-exit time.
3201
+ *
3202
+ * @param task - Subagent task specification.
3203
+ * @param opts - Per-call streaming and cleanup overrides.
3204
+ * @returns A live subagent handle.
3205
+ */
3206
+ spawnSubagent(task: SubagentTask, opts?: SubagentSpawnOptions): Promise<SubagentHandle>;
3207
+ /**
3208
+ * Race a set of subagent handles, returning the first one that exits.
2117
3209
  *
2118
3210
  * @remarks
2119
- * Emits a SCAFFOLD Pi extension file under `extensions/mcp-<name>.ts`.
2120
- * The scaffold registers a Pi tool whose `execute` function currently
2121
- * returns an "isError" payload explaining that the MCP bridge runtime
2122
- * is not yet implemented. This preserves the public lifecycle surface
2123
- * (install/list/remove) so orchestration code can treat the bridge as
2124
- * a first-class asset while the concrete JSON-RPC runtime is built out
2125
- * in a later wave.
3211
+ * Maps CANT's `parallel: race` construct (per ADR-035 §D6) onto the
3212
+ * canonical {@link spawnSubagent} contract. The losing handles are
3213
+ * gracefully terminated via {@link SubagentHandle.terminate} once the
3214
+ * first settles so no straggler children outlive the race.
3215
+ *
3216
+ * @param handles - Subagent handles to race.
3217
+ * @returns The {@link SubagentExitResult} of the first child to exit.
3218
+ * @throws When `handles` is empty (caller bug — a race over zero
3219
+ * children has no winner).
2126
3220
  */
2127
- installMcpAsExtension(server: McpServerSpec, scope: HarnessScope): Promise<void>;
3221
+ static raceSubagents(handles: SubagentHandle[]): Promise<SubagentExitResult>;
2128
3222
  /**
2129
- * {@inheritDoc Harness.spawnSubagent}
3223
+ * Settle a set of subagent handles, returning a parallel array of
3224
+ * results.
2130
3225
  *
2131
3226
  * @remarks
2132
- * Invokes Pi's configured `spawnCommand` (e.g.
2133
- * `["pi", "--mode", "json", "-p", "--no-session"]`) with the task prompt
2134
- * appended as the trailing positional argument. The {@link SubagentTask.targetProviderId}
2135
- * is a routing hint carried in the prompt stream; Pi's own extension
2136
- * layer dispatches to the correct inner agent.
3227
+ * Maps CANT's `parallel: settle` construct (per ADR-035 §D6) onto the
3228
+ * canonical {@link spawnSubagent} contract. Because
3229
+ * {@link SubagentHandle.exitPromise} never rejects, every entry in
3230
+ * the returned array is `{ status: 'fulfilled', value: ... }` under
3231
+ * normal operation; the `PromiseSettledResult` shape is preserved
3232
+ * for forward compatibility with future failure modes.
3233
+ *
3234
+ * @param handles - Subagent handles to settle.
3235
+ * @returns Parallel array of settled exit results, one per input.
3236
+ */
3237
+ static settleAllSubagents(handles: SubagentHandle[]): Promise<PromiseSettledResult<SubagentExitResult>[]>;
3238
+ /**
3239
+ * Per-line stdout dispatcher used by the streaming buffer flusher.
2137
3240
  *
2138
- * Throws immediately when the provider entry is missing a `spawnCommand`
2139
- * so callers see configuration errors early rather than at child-exit time.
3241
+ * @remarks
3242
+ * Extracted as a private method so the line-handling logic stays
3243
+ * close to {@link spawnSubagent} but does not bloat the parent
3244
+ * function. Skips empty lines (a leading newline produces a zero-
3245
+ * length entry that has no semantic meaning).
2140
3246
  */
2141
- spawnSubagent(task: SubagentTask): Promise<SubagentHandle>;
3247
+ private handleStdoutLine;
2142
3248
  /** {@inheritDoc Harness.readSettings} */
2143
3249
  readSettings(scope: HarnessScope): Promise<unknown>;
2144
3250
  /** {@inheritDoc Harness.writeSettings} */
2145
3251
  writeSettings(patch: Record<string, unknown>, scope: HarnessScope): Promise<void>;
2146
3252
  /** {@inheritDoc Harness.configureModels} */
2147
3253
  configureModels(modelPatterns: string[], scope: HarnessScope): Promise<void>;
3254
+ /**
3255
+ * Resolve the `models.json` path for a given legacy two-tier scope.
3256
+ *
3257
+ * @remarks
3258
+ * Lives next to `settings.json`. Global scope uses the Pi state root,
3259
+ * project scope uses the project's `.pi/` directory, matching the
3260
+ * dual-file authority model documented in ADR-035 §D3.
3261
+ */
3262
+ private modelsConfigPath;
3263
+ /**
3264
+ * Resolve the sessions directory — always user-tier because Pi owns
3265
+ * session storage and the three-tier model folds session listings to
3266
+ * the single authoritative location per ADR-035 §D2.
3267
+ */
3268
+ private sessionsDir;
3269
+ /** {@inheritDoc Harness.installExtension} */
3270
+ installExtension(sourcePath: string, name: string, tier: HarnessTier, projectDir?: string, opts?: HarnessInstallOptions): Promise<{
3271
+ targetPath: string;
3272
+ tier: HarnessTier;
3273
+ }>;
3274
+ /** {@inheritDoc Harness.removeExtension} */
3275
+ removeExtension(name: string, tier: HarnessTier, projectDir?: string): Promise<boolean>;
3276
+ /** {@inheritDoc Harness.listExtensions} */
3277
+ listExtensions(projectDir?: string): Promise<ExtensionEntry[]>;
3278
+ /** {@inheritDoc Harness.listSessions} */
3279
+ listSessions(opts?: {
3280
+ includeSubagents?: boolean;
3281
+ }): Promise<SessionSummary[]>;
3282
+ /** {@inheritDoc Harness.showSession} */
3283
+ showSession(id: string): Promise<SessionDocument>;
3284
+ /** {@inheritDoc Harness.readModelsConfig} */
3285
+ readModelsConfig(scope: HarnessScope): Promise<PiModelsConfig>;
3286
+ /** {@inheritDoc Harness.writeModelsConfig} */
3287
+ writeModelsConfig(config: PiModelsConfig, scope: HarnessScope): Promise<void>;
3288
+ /** {@inheritDoc Harness.listModels} */
3289
+ listModels(scope: HarnessScope): Promise<ModelListEntry[]>;
3290
+ /** {@inheritDoc Harness.installPrompt} */
3291
+ installPrompt(sourceDir: string, name: string, tier: HarnessTier, projectDir?: string, opts?: HarnessInstallOptions): Promise<{
3292
+ targetPath: string;
3293
+ tier: HarnessTier;
3294
+ }>;
3295
+ /** {@inheritDoc Harness.listPrompts} */
3296
+ listPrompts(projectDir?: string): Promise<PromptEntry[]>;
3297
+ /** {@inheritDoc Harness.removePrompt} */
3298
+ removePrompt(name: string, tier: HarnessTier, projectDir?: string): Promise<boolean>;
3299
+ /** {@inheritDoc Harness.installTheme} */
3300
+ installTheme(sourceFile: string, name: string, tier: HarnessTier, projectDir?: string, opts?: HarnessInstallOptions): Promise<{
3301
+ targetPath: string;
3302
+ tier: HarnessTier;
3303
+ }>;
3304
+ /** {@inheritDoc Harness.listThemes} */
3305
+ listThemes(projectDir?: string): Promise<ThemeEntry[]>;
3306
+ /** {@inheritDoc Harness.removeTheme} */
3307
+ removeTheme(name: string, tier: HarnessTier, projectDir?: string): Promise<boolean>;
3308
+ /**
3309
+ * {@inheritDoc Harness.installCantProfile}
3310
+ *
3311
+ * @remarks
3312
+ * Validates the source via {@link validateCantProfile} before copying so
3313
+ * we never persist a `.cant` file the runtime bridge cannot load. The
3314
+ * target layout is `<tier-root>/cant/<name>.cant`, resolved through
3315
+ * {@link resolveTierDir} so the project/user/global hierarchy stays
3316
+ * consistent with the other Wave-1 verbs.
3317
+ */
3318
+ installCantProfile(sourcePath: string, name: string, tier: HarnessTier, projectDir?: string, opts?: HarnessInstallOptions): Promise<{
3319
+ targetPath: string;
3320
+ tier: HarnessTier;
3321
+ counts: CantProfileCounts;
3322
+ }>;
3323
+ /** {@inheritDoc Harness.removeCantProfile} */
3324
+ removeCantProfile(name: string, tier: HarnessTier, projectDir?: string): Promise<boolean>;
3325
+ /**
3326
+ * {@inheritDoc Harness.listCantProfiles}
3327
+ *
3328
+ * @remarks
3329
+ * Walks every tier in {@link TIER_PRECEDENCE} order, parsing each
3330
+ * discovered `.cant` file via cant-core to extract a
3331
+ * {@link CantProfileCounts} bag. Higher-precedence tiers shadow
3332
+ * lower-precedence entries with the same name; shadowed entries
3333
+ * still appear in the result but carry the
3334
+ * `shadowedByHigherTier` flag so callers can render the precedence
3335
+ * story without losing visibility of the duplicate.
3336
+ */
3337
+ listCantProfiles(projectDir?: string): Promise<CantProfileEntry[]>;
3338
+ /**
3339
+ * {@inheritDoc Harness.validateCantProfile}
3340
+ *
3341
+ * @remarks
3342
+ * Pure validator. Reads the file, runs `parseDocument` to derive
3343
+ * counts (when parsing succeeds) and `validateDocument` to collect
3344
+ * the 42-rule diagnostic feed. The two calls are kept independent so
3345
+ * we can still report counts for files that pass parsing but fail a
3346
+ * lint rule.
3347
+ */
3348
+ validateCantProfile(sourcePath: string): Promise<ValidateCantProfileResult>;
2148
3349
  }
2149
3350
 
2150
3351
  /**
@@ -2226,42 +3427,52 @@ declare function getPrimaryHarness(): Harness | null;
2226
3427
  declare function getAllHarnesses(): Harness[];
2227
3428
  /**
2228
3429
  * Resolve the default set of target providers when the user has not passed
2229
- * `--agent`.
3430
+ * `--agent`, honouring the active {@link ExclusivityMode}.
2230
3431
  *
2231
3432
  * @remarks
2232
- * Resolution policy:
3433
+ * Resolution policy is layered. The active {@link ExclusivityMode} (read
3434
+ * via {@link getExclusivityMode}) selects which branch of the matrix runs:
3435
+ *
3436
+ * | Mode | Pi installed | Pi absent |
3437
+ * |---|---|---|
3438
+ * | `'auto'` (default) | Returns `[piProvider]`. Explicit non-Pi targets emit a one-time deprecation warning per process. | Falls back to installed primary/high-tier providers (legacy v2026.4.5 behaviour) and emits a one-time boot warning. |
3439
+ * | `'force-pi'` | Returns `[piProvider]`. | Throws {@link PiRequiredError}. |
3440
+ * | `'legacy'` | Returns the full installed provider list in priority order (matches pre-exclusivity behaviour). | Same. |
2233
3441
  *
2234
- * 1. If the registry's primary harness (the provider with
2235
- * `priority === "primary"`) is installed on the current system,
2236
- * return `[primaryProvider]` so that commands dispatch to the
2237
- * primary harness by default.
2238
- * 2. Otherwise, return the set of installed providers at priority
2239
- * `"primary"` or `"high"`. This restores the legacy "detected high-tier
2240
- * providers" fallback that CAAMP has always used when no primary
2241
- * harness is available.
2242
- * 3. If the priority filter yields an empty list (e.g. in tests that stub
2243
- * providers without a `priority` field, or in fresh installs with only
2244
- * medium/low-tier providers detected), fall back to the full installed
2245
- * provider list so that commands retain a valid target.
3442
+ * **Install paths are unaffected.** Per ADR-035 §D7, this helper governs
3443
+ * RUNTIME INVOCATION dispatch only. Skill and instruction install
3444
+ * dispatchers ({@link dispatchInstallSkillAcrossProviders},
3445
+ * {@link dispatchRemoveSkillAcrossProviders}) intentionally do not call
3446
+ * this function they target every requested provider directly so that
3447
+ * users in `force-pi` mode can still `caamp skills install foo --agent
3448
+ * claude-code` while Pi is being installed.
2246
3449
  *
2247
- * This helper is intentionally defensive: it swallows registry-related
2248
- * exceptions (returning an empty-primary-harness result) so stubbed test
2249
- * environments that do not wire the full provider registry still behave
2250
- * sensibly.
3450
+ * The helper is intentionally defensive: registry/detection exceptions
3451
+ * are caught and treated as "Pi unknown" so stubbed test environments
3452
+ * that do not wire the full registry still behave sensibly.
2251
3453
  *
3454
+ * @param options - Optional explicit provider selection (e.g. from
3455
+ * `--agent`) used by `auto`-mode deprecation warning detection. Omit to
3456
+ * request the implicit default resolution.
2252
3457
  * @returns Ordered list of providers to target by default.
3458
+ * @throws {@link PiRequiredError} when mode is `'force-pi'` and Pi is not
3459
+ * installed.
2253
3460
  *
2254
3461
  * @example
2255
3462
  * ```typescript
3463
+ * // Implicit default — used by `caamp skills list` and friends.
2256
3464
  * const targets = resolveDefaultTargetProviders();
2257
- * if (targets.length === 0) {
2258
- * console.error("No target providers found. Use --agent or --all.");
2259
- * }
3465
+ *
3466
+ * // Explicit user selection emits a deprecation warning in `auto` mode
3467
+ * // when the selection excludes Pi and Pi is installed.
3468
+ * const explicit = resolveDefaultTargetProviders({
3469
+ * explicit: [getProvider('claude-code')!],
3470
+ * });
2260
3471
  * ```
2261
3472
  *
2262
3473
  * @public
2263
3474
  */
2264
- declare function resolveDefaultTargetProviders(): Provider[];
3475
+ declare function resolveDefaultTargetProviders(options?: ResolveDefaultTargetProvidersOptions): Provider[];
2265
3476
 
2266
3477
  /**
2267
3478
  * All hook event categories (8 total).
@@ -3572,6 +4783,362 @@ declare class MarketplaceClient {
3572
4783
  getSkill(scopedName: string): Promise<MarketplaceResult | null>;
3573
4784
  }
3574
4785
 
4786
+ /**
4787
+ * MCP server config reader.
4788
+ *
4789
+ * @remarks
4790
+ * Reads MCP server entries from a provider's per-agent config file using
4791
+ * the format-agnostic {@link readConfig} substrate from `core/formats`.
4792
+ * The provider's `capabilities.mcp` block is the single source of truth
4793
+ * for the file path, format, and dot-notation key — this module never
4794
+ * hard-codes provider-specific layout.
4795
+ *
4796
+ * Three operations are exported:
4797
+ *
4798
+ * - {@link listMcpServers} — enumerate every server entry on a single
4799
+ * provider's config file at a given scope.
4800
+ * - {@link listAllMcpServers} — fan out across every MCP-capable
4801
+ * provider in the registry, returning a map keyed by provider id.
4802
+ * - {@link detectMcpInstallations} — lighter-weight scan that just
4803
+ * reports which providers currently have any MCP config files on
4804
+ * disk (used by `caamp mcp detect`).
4805
+ *
4806
+ * @packageDocumentation
4807
+ */
4808
+
4809
+ /**
4810
+ * Scope identifier for MCP config file resolution.
4811
+ *
4812
+ * @remarks
4813
+ * Mirrors the two-tier scope model the underlying provider config files
4814
+ * already use: `project` reads `<projectDir>/<provider.configPathProject>`
4815
+ * and `global` reads the absolute `provider.configPathGlobal` path. The
4816
+ * three-tier {@link HarnessTier} model used by the Pi extensions
4817
+ * verbs is intentionally not adopted here — MCP config files are owned
4818
+ * by individual tools and live on those tools' two-tier hierarchy, not
4819
+ * on a CleoOS-managed hub.
4820
+ *
4821
+ * @public
4822
+ */
4823
+ type McpScope = 'project' | 'global';
4824
+ /**
4825
+ * Result of a single provider's MCP installation probe.
4826
+ *
4827
+ * @remarks
4828
+ * Returned by {@link detectMcpInstallations} for each provider that
4829
+ * declares an MCP capability in the registry. The `configPath` field
4830
+ * is the resolved file path the probe inspected; `exists` indicates
4831
+ * whether the file is present on disk; `serverCount` is `null` when
4832
+ * the file is missing or unparseable, otherwise the number of server
4833
+ * entries found at the dot-notation key.
4834
+ *
4835
+ * @public
4836
+ */
4837
+ interface McpDetectionEntry {
4838
+ /** Provider id (e.g. `"claude-code"`). */
4839
+ providerId: string;
4840
+ /** Human-readable provider name. */
4841
+ providerName: string;
4842
+ /** Resolved scope of the probed config file. */
4843
+ scope: McpScope;
4844
+ /** Absolute path to the provider's MCP config file. */
4845
+ configPath: string;
4846
+ /** Whether the config file exists on disk. */
4847
+ exists: boolean;
4848
+ /** Number of server entries found, or `null` when the file is missing/unparseable. */
4849
+ serverCount: number | null;
4850
+ /** ISO 8601 timestamp of the file's last modification, or `null` when the file is missing. */
4851
+ lastModified: string | null;
4852
+ }
4853
+ /**
4854
+ * Resolve a provider's MCP config file path for the given scope, or
4855
+ * `null` if the provider does not declare an MCP capability or the
4856
+ * scope is unsupported.
4857
+ *
4858
+ * @remarks
4859
+ * Thin wrapper over {@link resolveProviderConfigPath} that filters out
4860
+ * providers without an MCP capability up front so callers can use a
4861
+ * single null check rather than two.
4862
+ *
4863
+ * @param provider - Provider to resolve a config path for.
4864
+ * @param scope - Scope to resolve.
4865
+ * @param projectDir - Project directory used for the `project` scope.
4866
+ * @returns The absolute config file path, or `null` when unavailable.
4867
+ *
4868
+ * @public
4869
+ */
4870
+ declare function resolveMcpConfigPath(provider: Provider, scope: McpScope, projectDir?: string): string | null;
4871
+ /**
4872
+ * List MCP server entries declared in a single provider's config file.
4873
+ *
4874
+ * @remarks
4875
+ * Reads the provider's MCP config file using the format-agnostic
4876
+ * {@link readConfig} substrate, walks the dot-notation
4877
+ * `provider.capabilities.mcp.configKey` to find the servers section,
4878
+ * and returns one {@link McpServerEntry} per child key.
4879
+ *
4880
+ * Returns an empty array (not an error) when:
4881
+ *
4882
+ * - the provider does not declare an MCP capability,
4883
+ * - the resolved config path is unavailable for the requested scope,
4884
+ * - the config file does not exist on disk,
4885
+ * - the config file is empty or unparseable,
4886
+ * - the config file exists but has no MCP servers section.
4887
+ *
4888
+ * "No file" is a normal state for an uninstalled tool, so callers
4889
+ * should treat the empty array as success and only escalate to an
4890
+ * error envelope when the user explicitly asked about a missing
4891
+ * provider.
4892
+ *
4893
+ * @param provider - Provider whose config file to read.
4894
+ * @param scope - Scope to resolve (project|global).
4895
+ * @param projectDir - Project directory used for the `project` scope
4896
+ * (defaults to `process.cwd()`).
4897
+ * @returns Array of MCP server entries, or `[]` when nothing was found.
4898
+ *
4899
+ * @public
4900
+ */
4901
+ declare function listMcpServers(provider: Provider, scope: McpScope, projectDir?: string): Promise<McpServerEntry[]>;
4902
+ /**
4903
+ * Map of provider id → MCP server entries for that provider.
4904
+ *
4905
+ * @remarks
4906
+ * Return shape of {@link listAllMcpServers}. Providers without an MCP
4907
+ * capability are intentionally absent from the map (rather than mapped
4908
+ * to an empty array) so callers can iterate the result and immediately
4909
+ * know which providers were probed.
4910
+ *
4911
+ * @public
4912
+ */
4913
+ type McpServerEntriesByProvider = Map<string, McpServerEntry[]>;
4914
+ /**
4915
+ * List MCP server entries for every MCP-capable provider in the
4916
+ * registry at the given scope.
4917
+ *
4918
+ * @remarks
4919
+ * Iterates {@link getAllProviders}, filters to those with a
4920
+ * `capabilities.mcp` block, calls {@link listMcpServers} on each, and
4921
+ * collects the results into a map keyed by provider id.
4922
+ *
4923
+ * Each provider is probed independently — a parse failure on one
4924
+ * provider will not affect the others. The result map only contains
4925
+ * entries for providers that were actually probed (i.e. had an MCP
4926
+ * capability), so consumers can iterate `result.entries()` without
4927
+ * skipping non-MCP providers.
4928
+ *
4929
+ * @param scope - Scope to resolve for every provider.
4930
+ * @param projectDir - Project directory used for the `project` scope.
4931
+ * @returns Map of provider id → server entries.
4932
+ *
4933
+ * @public
4934
+ */
4935
+ declare function listAllMcpServers(scope: McpScope, projectDir?: string): Promise<McpServerEntriesByProvider>;
4936
+ /**
4937
+ * Probe every MCP-capable provider in the registry to determine which
4938
+ * ones have a config file on disk and how many servers are configured.
4939
+ *
4940
+ * @remarks
4941
+ * Lightweight detection used by `caamp mcp detect`. Unlike
4942
+ * {@link listAllMcpServers} this does not return individual server
4943
+ * entries — it just reports a count plus the file mtime so callers can
4944
+ * answer "which tools on my machine already have MCP configured?"
4945
+ * without paying the cost of materialising every server entry.
4946
+ *
4947
+ * @param scope - Scope to probe.
4948
+ * @param projectDir - Project directory used for the `project` scope.
4949
+ * @returns Array of detection entries, one per MCP-capable provider.
4950
+ *
4951
+ * @public
4952
+ */
4953
+ declare function detectMcpInstallations(scope: McpScope, projectDir?: string): Promise<McpDetectionEntry[]>;
4954
+
4955
+ /**
4956
+ * MCP server config installer.
4957
+ *
4958
+ * @remarks
4959
+ * Writes a single MCP server entry into a provider's config file using
4960
+ * the format-agnostic {@link writeConfig} substrate from `core/formats`.
4961
+ * The provider's `capabilities.mcp` block is the single source of
4962
+ * truth for the file path, format, and dot-notation key.
4963
+ *
4964
+ * Conflict-on-write semantics:
4965
+ *
4966
+ * - When the target server name does not yet exist in the file, the
4967
+ * write succeeds and {@link InstallMcpServerResult.conflicted} is
4968
+ * `false`.
4969
+ * - When the target server name already exists in the file and `force`
4970
+ * is `false`, the installer DOES NOT write — it returns
4971
+ * `installed: false, conflicted: true` so the caller can emit a
4972
+ * typed conflict error envelope.
4973
+ * - When `force` is `true`, an existing entry is overwritten and the
4974
+ * result reports `installed: true, conflicted: true` so the caller
4975
+ * can surface the overwrite in its envelope.
4976
+ *
4977
+ * Parent directories of the resolved config path are created lazily on
4978
+ * write — see {@link writeConfig} for the per-format details.
4979
+ *
4980
+ * @packageDocumentation
4981
+ */
4982
+
4983
+ /**
4984
+ * Options accepted by {@link installMcpServer}.
4985
+ *
4986
+ * @public
4987
+ */
4988
+ interface InstallMcpServerOptions {
4989
+ /** Scope to write to (project|global). */
4990
+ scope: McpScope;
4991
+ /** When `true`, overwrite an existing server entry instead of failing. */
4992
+ force?: boolean;
4993
+ /** Project directory used for the `project` scope. */
4994
+ projectDir?: string;
4995
+ }
4996
+ /**
4997
+ * Result of an {@link installMcpServer} call.
4998
+ *
4999
+ * @remarks
5000
+ * `installed` is `true` only when the file was actually written.
5001
+ * `conflicted` is `true` whenever the target server name was already
5002
+ * present, regardless of whether the write went through (force was
5003
+ * supplied) or was suppressed (force was withheld).
5004
+ *
5005
+ * @public
5006
+ */
5007
+ interface InstallMcpServerResult {
5008
+ /** Whether the entry was written to the config file. */
5009
+ installed: boolean;
5010
+ /** Whether the target server name already existed before the call. */
5011
+ conflicted: boolean;
5012
+ /** Absolute path to the config file that was (or would have been) written. */
5013
+ sourcePath: string;
5014
+ /** Provider id the entry was written for. */
5015
+ providerId: string;
5016
+ /** Server name that was written. */
5017
+ serverName: string;
5018
+ }
5019
+ /**
5020
+ * Install an MCP server entry into a single provider's config file.
5021
+ *
5022
+ * @remarks
5023
+ * Resolves the provider's MCP config path for the requested scope,
5024
+ * checks for an existing entry with the same server name, and either
5025
+ * writes the new config (when no conflict, or when `force` is set) or
5026
+ * returns a non-installed conflict result.
5027
+ *
5028
+ * Throws a plain `Error` (not a `LAFSCommandError`) when the provider
5029
+ * has no MCP capability or no config path for the requested scope —
5030
+ * those are caller-side validation failures and should be caught and
5031
+ * re-thrown as typed `LAFSCommandError`s in the command layer.
5032
+ *
5033
+ * @param provider - Target provider.
5034
+ * @param serverName - Name/key for the new server entry.
5035
+ * @param config - Canonical {@link McpServerConfig} payload to write.
5036
+ * @param opts - Install options (scope, force, projectDir).
5037
+ * @returns Structured install result describing what happened.
5038
+ * @throws `Error` when the provider has no MCP capability or no
5039
+ * project-scoped config path is available.
5040
+ *
5041
+ * @public
5042
+ */
5043
+ declare function installMcpServer(provider: Provider, serverName: string, config: McpServerConfig, opts: InstallMcpServerOptions): Promise<InstallMcpServerResult>;
5044
+
5045
+ /**
5046
+ * MCP server config remover.
5047
+ *
5048
+ * @remarks
5049
+ * Removes a single MCP server entry from a provider's config file
5050
+ * using the format-agnostic {@link removeConfig} substrate from
5051
+ * `core/formats`. The provider's `capabilities.mcp` block is the
5052
+ * single source of truth for the file path, format, and dot-notation
5053
+ * key.
5054
+ *
5055
+ * Both single-provider and all-providers variants are exported. Both
5056
+ * are idempotent: removing a server that does not exist returns
5057
+ * `removed: false` rather than throwing.
5058
+ *
5059
+ * @packageDocumentation
5060
+ */
5061
+
5062
+ /**
5063
+ * Options accepted by {@link removeMcpServer} and
5064
+ * {@link removeMcpServerFromAll}.
5065
+ *
5066
+ * @public
5067
+ */
5068
+ interface RemoveMcpServerOptions {
5069
+ /** Scope to target (project|global). */
5070
+ scope: McpScope;
5071
+ /** Project directory used for the `project` scope. */
5072
+ projectDir?: string;
5073
+ }
5074
+ /**
5075
+ * Result of a single-provider {@link removeMcpServer} call.
5076
+ *
5077
+ * @remarks
5078
+ * `removed` is `true` only when an entry was actually deleted from
5079
+ * the file. The `reason` field carries an optional discriminator when
5080
+ * the call was a no-op so the command layer can surface a precise
5081
+ * envelope (e.g. "no config file" vs "entry not present").
5082
+ *
5083
+ * @public
5084
+ */
5085
+ interface RemoveMcpServerResult {
5086
+ /** Provider id the call targeted. */
5087
+ providerId: string;
5088
+ /** Server name the call targeted. */
5089
+ serverName: string;
5090
+ /** Resolved config file path, or `null` when the provider had no MCP capability. */
5091
+ sourcePath: string | null;
5092
+ /** Whether an entry was actually deleted. */
5093
+ removed: boolean;
5094
+ /**
5095
+ * Diagnostic discriminator when `removed` is `false`.
5096
+ *
5097
+ * - `"no-mcp-capability"` — provider does not consume MCP servers
5098
+ * - `"no-config-path"` — provider has no config path for the scope
5099
+ * - `"file-missing"` — config file does not exist on disk
5100
+ * - `"entry-missing"` — config file exists but had no matching entry
5101
+ *
5102
+ * Set to `null` when `removed` is `true`.
5103
+ */
5104
+ reason: 'no-mcp-capability' | 'no-config-path' | 'file-missing' | 'entry-missing' | null;
5105
+ }
5106
+ /**
5107
+ * Remove an MCP server entry from a single provider's config file.
5108
+ *
5109
+ * @remarks
5110
+ * Idempotent: when the entry is not present (or the file is missing
5111
+ * entirely) the call returns `removed: false` with a structured
5112
+ * `reason` rather than throwing.
5113
+ *
5114
+ * @param provider - Provider whose config file to modify.
5115
+ * @param serverName - Server name/key to remove.
5116
+ * @param opts - Removal options.
5117
+ * @returns Structured result describing whether the entry was removed.
5118
+ *
5119
+ * @public
5120
+ */
5121
+ declare function removeMcpServer(provider: Provider, serverName: string, opts: RemoveMcpServerOptions): Promise<RemoveMcpServerResult>;
5122
+ /**
5123
+ * Remove an MCP server entry from every MCP-capable provider in the
5124
+ * registry that currently has it configured.
5125
+ *
5126
+ * @remarks
5127
+ * Iterates {@link getAllProviders}, calls {@link removeMcpServer} on
5128
+ * each MCP-capable provider, and collects the per-provider results.
5129
+ * Each provider is processed independently — a failure on one does
5130
+ * not abort the others. The result array contains one entry per
5131
+ * MCP-capable provider, even when the entry was not present (so
5132
+ * callers can render a complete report).
5133
+ *
5134
+ * @param serverName - Server name/key to remove from every provider.
5135
+ * @param opts - Removal options applied uniformly to every provider.
5136
+ * @returns Array of per-provider removal results.
5137
+ *
5138
+ * @public
5139
+ */
5140
+ declare function removeMcpServerFromAll(serverName: string, opts: RemoveMcpServerOptions): Promise<RemoveMcpServerResult[]>;
5141
+
3575
5142
  /**
3576
5143
  * Scope for path resolution, either global (user home) or project-local.
3577
5144
  *
@@ -6345,4 +7912,4 @@ declare function parseSource(input: string): ParsedSource;
6345
7912
  */
6346
7913
  declare function isMarketplaceScoped(input: string): boolean;
6347
7914
 
6348
- export { type AuditFinding, type AuditResult, type AuditRule, type AuditSeverity, type BatchInstallOptions, type BatchInstallResult, CANONICAL_HOOK_EVENTS, type CaampLockFile, type CanonicalEventDefinition, type CanonicalHookEvent, type ConfigFormat, type CrossProviderMatrix, type CtDispatchMatrix, type CtManifest, type CtManifestSkill, type CtProfileDefinition, type CtSkillEntry, type CtValidationIssue, type CtValidationResult, type DetectionCacheOptions, type DetectionResult, type EnsureProviderInstructionFileOptions, type EnsureProviderInstructionFileResult, type GlobalOptions, HOOK_CATEGORIES, type Harness, type HarnessScope, type HookCategory, type HookEvent, type HookHandlerType, type HookMapping, type HookSupportResult, type HookSystemType, type InjectionCheckResult, type InjectionStatus, type InjectionTemplate, type InstructionUpdateSummary, type LockEntry, MarketplaceClient, type MarketplaceResult, type MarketplaceSearchResult, type MarketplaceSkill, type McpConfigFormat, type McpServerConfig, type McpServerEntry, type McpServerSpec, type McpTransportType, type NormalizedHookEvent, type NormalizedRecommendationCriteria, type ParsedSource, PiHarness, type PlatformPaths, type Provider, type ProviderCapabilities, type ProviderHarnessCapability, type ProviderHookProfile, type ProviderHookSummary, type ProviderHooksCapability, type ProviderMcpCapability, type ProviderPriority, type ProviderSkillsCapability, type ProviderSpawnCapability, type ProviderStatus, RECOMMENDATION_ERROR_CODES, type RankedSkillRecommendation, type RecommendSkillsResult, type RecommendationCriteriaInput, type RecommendationErrorCode, type RecommendationOptions, type RecommendationReason, type RecommendationReasonCode, type RecommendationScoreBreakdown, type RecommendationValidationIssue, type RecommendationValidationResult, type RecommendationWeights, type RegistryHarnessKind, type RegistryHookCatalog, type RegistryHookFormat, type SkillBatchOperation, type SkillEntry, type SkillInstallResult, type SkillIntegrityResult, type SkillIntegrityStatus, type SkillLibrary, type SkillLibraryDispatchMatrix, type SkillLibraryEntry, type SkillLibraryManifest, type SkillLibraryManifestSkill, type SkillLibraryProfile, type SkillLibraryValidationIssue, type SkillLibraryValidationResult, type SkillMetadata, type SkillsPrecedence, type SourceType, type SpawnAdapter, type SpawnMechanism, type SpawnOptions, type SpawnResult, type SubagentHandle, type SubagentResult, type SubagentTask, type SystemInfo, type TransportType, type ValidationIssue, type ValidationResult, _resetPlatformPathsCache, buildHookMatrix, buildInjectionContent, buildLibraryFromFiles, buildSkillsMap, catalog, checkAllInjections, checkAllSkillIntegrity, checkAllSkillUpdates, checkInjection, checkSkillIntegrity, checkSkillUpdate, clearRegisteredLibrary, deepMerge, detectAllProviders, detectProjectProviders, detectProvider, discoverSkill, discoverSkills, ensureAllProviderInstructionFiles, ensureDir, ensureProviderInstructionFile, formatSkillRecommendations, generateInjectionContent, generateSkillsSection, getAgentsConfigPath, getAgentsHome, getAgentsInstructFile, getAgentsLinksDir, getAgentsMcpDir, getAgentsMcpServersPath, getAgentsSpecDir, getAgentsWikiDir, getAllCanonicalEvents, getAllHarnesses, getAllProviders, getCanonicalEvent, getCanonicalEventsByCategory, getCanonicalSkillsDir, getCommonEvents, getCommonHookEvents, getEffectiveSkillsPaths, getHarnessFor, getHookConfigPath, getHookMappingsVersion, getHookSupport, getHookSystemType, getInstalledProviders, getInstructionFiles, getLockFilePath, getMappedProviderIds, getNestedValue, getPlatformLocations, getPlatformPaths, getPrimaryHarness, getPrimaryProvider, getProjectAgentsDir, getProvider, getProviderCapabilities, getProviderCount, getProviderHookProfile, getProviderOnlyEvents, getProviderSummary, getProvidersByHookEvent, getProvidersByInstructFile, getProvidersByPriority, getProvidersBySkillsPrecedence, getProvidersBySpawnCapability, getProvidersByStatus, getProvidersForEvent, getRegistryVersion, getSpawnCapableProviders, getSupportedEvents, getSystemInfo, getTrackedSkills, getUnsupportedEvents, groupByInstructFile, inject, injectAll, installBatchWithRollback, installSkill, isCaampOwnedSkill, isMarketplaceScoped, isQuiet, isVerbose, listCanonicalSkills, loadLibraryFromModule, normalizeRecommendationCriteria, parseInjectionContent, parseSkillFile, parseSource, providerSupports, providerSupportsById, rankSkills, readConfig, recommendSkills, recordSkillInstall, registerSkillLibrary, registerSkillLibraryFromPath, removeConfig, removeInjection, removeSkill, removeSkillFromLock, resetDetectionCache, resolveAlias, resolveDefaultTargetProviders, resolveNativeEvent, resolveProviderSkillsDirs, resolveRegistryTemplatePath, scanDirectory, scanFile, scoreSkillRecommendation, searchSkills, selectProvidersByMinimumPriority, setQuiet, setVerbose, shouldOverrideSkill, supportsHook, toCanonical, toNative, toNativeBatch, toSarif, tokenizeCriteriaValue, translateToAll, updateInstructionsSingleOperation, validateInstructionIntegrity, validateRecommendationCriteria, validateSkill, writeConfig };
7915
+ export { type AuditFinding, type AuditResult, type AuditRule, type AuditSeverity, type BatchInstallOptions, type BatchInstallResult, CANONICAL_HOOK_EVENTS, type CaampLockFile, type CanonicalEventDefinition, type CanonicalHookEvent, type CantProfileCounts, type CantProfileEntry, type CantValidationDiagnostic, type ConfigFormat, type CrossProviderMatrix, type CtDispatchMatrix, type CtManifest, type CtManifestSkill, type CtProfileDefinition, type CtSkillEntry, type CtValidationIssue, type CtValidationResult, DEFAULT_EXCLUSIVITY_MODE, type DetectionCacheOptions, type DetectionResult, EXCLUSIVITY_MODE_ENV_VAR, type EnsureProviderInstructionFileOptions, type EnsureProviderInstructionFileResult, type ExclusivityMode, type GlobalOptions, HOOK_CATEGORIES, type Harness, type HarnessScope, type HookCategory, type HookEvent, type HookHandlerType, type HookMapping, type HookSupportResult, type HookSystemType, type InjectionCheckResult, type InjectionStatus, type InjectionTemplate, type InstallMcpServerOptions, type InstallMcpServerResult, type InstructionUpdateSummary, type LockEntry, MarketplaceClient, type MarketplaceResult, type MarketplaceSearchResult, type MarketplaceSkill, type McpConfigFormat, type McpDetectionEntry, type McpScope, type McpServerConfig, type McpServerEntriesByProvider, type McpServerEntry, type McpTransportType, type NormalizedHookEvent, type NormalizedRecommendationCriteria, type ParsedSource, PiHarness, PiRequiredError, type PlatformPaths, type Provider, type ProviderCapabilities, type ProviderHarnessCapability, type ProviderHookProfile, type ProviderHookSummary, type ProviderHooksCapability, type ProviderMcpCapability, type ProviderPriority, type ProviderSkillsCapability, type ProviderSpawnCapability, type ProviderStatus, RECOMMENDATION_ERROR_CODES, type RankedSkillRecommendation, type RecommendSkillsResult, type RecommendationCriteriaInput, type RecommendationErrorCode, type RecommendationOptions, type RecommendationReason, type RecommendationReasonCode, type RecommendationScoreBreakdown, type RecommendationValidationIssue, type RecommendationValidationResult, type RecommendationWeights, type RegistryHarnessKind, type RegistryHookCatalog, type RegistryHookFormat, type RemoveMcpServerOptions, type RemoveMcpServerResult, type ResolveDefaultTargetProvidersOptions, type SkillBatchOperation, type SkillEntry, type SkillInstallResult, type SkillIntegrityResult, type SkillIntegrityStatus, type SkillLibrary, type SkillLibraryDispatchMatrix, type SkillLibraryEntry, type SkillLibraryManifest, type SkillLibraryManifestSkill, type SkillLibraryProfile, type SkillLibraryValidationIssue, type SkillLibraryValidationResult, type SkillMetadata, type SkillsPrecedence, type SourceType, type SpawnAdapter, type SpawnMechanism, type SpawnOptions, type SpawnResult, type SubagentHandle, type SubagentResult, type SubagentTask, type SystemInfo, type TransportType, type ValidateCantProfileResult, type ValidationIssue, type ValidationResult, _resetPlatformPathsCache, buildHookMatrix, buildInjectionContent, buildLibraryFromFiles, buildSkillsMap, catalog, checkAllInjections, checkAllSkillIntegrity, checkAllSkillUpdates, checkInjection, checkSkillIntegrity, checkSkillUpdate, clearRegisteredLibrary, deepMerge, detectAllProviders, detectMcpInstallations, detectProjectProviders, detectProvider, discoverSkill, discoverSkills, ensureAllProviderInstructionFiles, ensureDir, ensureProviderInstructionFile, formatSkillRecommendations, generateInjectionContent, generateSkillsSection, getAgentsConfigPath, getAgentsHome, getAgentsInstructFile, getAgentsLinksDir, getAgentsMcpDir, getAgentsMcpServersPath, getAgentsSpecDir, getAgentsWikiDir, getAllCanonicalEvents, getAllHarnesses, getAllProviders, getCanonicalEvent, getCanonicalEventsByCategory, getCanonicalSkillsDir, getCommonEvents, getCommonHookEvents, getEffectiveSkillsPaths, getExclusivityMode, getHarnessFor, getHookConfigPath, getHookMappingsVersion, getHookSupport, getHookSystemType, getInstalledProviders, getInstructionFiles, getLockFilePath, getMappedProviderIds, getNestedValue, getPlatformLocations, getPlatformPaths, getPrimaryHarness, getPrimaryProvider, getProjectAgentsDir, getProvider, getProviderCapabilities, getProviderCount, getProviderHookProfile, getProviderOnlyEvents, getProviderSummary, getProvidersByHookEvent, getProvidersByInstructFile, getProvidersByPriority, getProvidersBySkillsPrecedence, getProvidersBySpawnCapability, getProvidersByStatus, getProvidersForEvent, getRegistryVersion, getSpawnCapableProviders, getSupportedEvents, getSystemInfo, getTrackedSkills, getUnsupportedEvents, groupByInstructFile, inject, injectAll, installBatchWithRollback, installMcpServer, installSkill, isCaampOwnedSkill, isExclusivityMode, isMarketplaceScoped, isQuiet, isVerbose, listAllMcpServers, listCanonicalSkills, listMcpServers, loadLibraryFromModule, normalizeRecommendationCriteria, parseInjectionContent, parseSkillFile, parseSource, providerSupports, providerSupportsById, rankSkills, readConfig, recommendSkills, recordSkillInstall, registerSkillLibrary, registerSkillLibraryFromPath, removeConfig, removeInjection, removeMcpServer, removeMcpServerFromAll, removeSkill, removeSkillFromLock, resetDetectionCache, resetExclusivityModeOverride, resolveAlias, resolveDefaultTargetProviders, resolveMcpConfigPath, resolveNativeEvent, resolveProviderSkillsDirs, resolveRegistryTemplatePath, scanDirectory, scanFile, scoreSkillRecommendation, searchSkills, selectProvidersByMinimumPriority, setExclusivityMode, setQuiet, setVerbose, shouldOverrideSkill, supportsHook, toCanonical, toNative, toNativeBatch, toSarif, tokenizeCriteriaValue, translateToAll, updateInstructionsSingleOperation, validateInstructionIntegrity, validateRecommendationCriteria, validateSkill, writeConfig };