@cleocode/caamp 2026.4.7 → 2026.4.10

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
@@ -1487,252 +1487,6 @@ interface InstructionUpdateSummary {
1487
1487
  */
1488
1488
  declare function updateInstructionsSingleOperation(providers: Provider[], content: string, scope?: Scope, projectDir?: string): Promise<InstructionUpdateSummary>;
1489
1489
 
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)
1502
- *
1503
- * @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
- * ```
1512
- *
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.
1518
- *
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
1522
- *
1523
- * @remarks
1524
- * Splits the key path on `.` and walks the object tree. Returns `undefined`
1525
- * at the first missing or non-object segment.
1526
- *
1527
- * @example
1528
- * ```typescript
1529
- * getNestedValue({ a: { b: { c: 42 } } }, "a.b.c"); // 42
1530
- * getNestedValue({ a: 1 }, "a.b"); // undefined
1531
- * ```
1532
- *
1533
- * @public
1534
- */
1535
- declare function getNestedValue(obj: Record<string, unknown>, keyPath: string): unknown;
1536
- /**
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)
1542
- *
1543
- * @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
- * ```
1551
- *
1552
- * @public
1553
- */
1554
- declare function ensureDir(filePath: string): Promise<void>;
1555
-
1556
- /**
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.
1560
- *
1561
- * @packageDocumentation
1562
- */
1563
-
1564
- /**
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
1573
- *
1574
- * @remarks
1575
- * Supported formats: `"json"`, `"jsonc"`, `"yaml"`, `"toml"`. Throws for
1576
- * any unrecognized format string.
1577
- *
1578
- * @example
1579
- * ```typescript
1580
- * const config = await readConfig("/path/to/config.json", "jsonc");
1581
- * ```
1582
- *
1583
- * @public
1584
- */
1585
- declare function readConfig(filePath: string, format: ConfigFormat): Promise<Record<string, unknown>>;
1586
- /**
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
1598
- *
1599
- * @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.
1602
- *
1603
- * @example
1604
- * ```typescript
1605
- * await writeConfig("/path/to/config.json", "jsonc", "mcpServers", "my-server", config);
1606
- * ```
1607
- *
1608
- * @public
1609
- */
1610
- declare function writeConfig(filePath: string, format: ConfigFormat, key: string, serverName: string, serverConfig: unknown): Promise<void>;
1611
- /**
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
1620
- *
1621
- * @remarks
1622
- * Delegates to the format-specific removal function. Returns `false` when the
1623
- * file does not exist or the entry is not found.
1624
- *
1625
- * @example
1626
- * ```typescript
1627
- * const removed = await removeConfig("/path/to/config.json", "jsonc", "mcpServers", "my-server");
1628
- * ```
1629
- *
1630
- * @public
1631
- */
1632
- declare function removeConfig(filePath: string, format: ConfigFormat, key: string, serverName: string): Promise<boolean>;
1633
-
1634
- /**
1635
- * Skill installer - canonical + symlink model
1636
- *
1637
- * Skills are stored once in a canonical location (.agents/skills/<name>/)
1638
- * and symlinked to each target agent's skills directory.
1639
- */
1640
-
1641
- /**
1642
- * Result of installing a skill to the canonical location and linking to agents.
1643
- *
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
- * ```
1652
- *
1653
- * @public
1654
- */
1655
- interface SkillInstallResult {
1656
- /** Skill name. */
1657
- 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;
1666
- }
1667
- /**
1668
- * Install a skill from a local path to the canonical location and link to agents.
1669
- *
1670
- * @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
1680
- *
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
- * ```
1688
- *
1689
- * @public
1690
- */
1691
- declare function installSkill(sourcePath: string, skillName: string, providers: Provider[], isGlobal: boolean, projectDir?: string): Promise<SkillInstallResult>;
1692
- /**
1693
- * Remove a skill from the canonical location and all agent symlinks.
1694
- *
1695
- * @remarks
1696
- * Removes symlinks from each provider's skills directory and then removes the
1697
- * canonical copy from the centralized canonical skills directory.
1698
- *
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
- * ```
1710
- *
1711
- * @public
1712
- */
1713
- declare function removeSkill(skillName: string, providers: Provider[], isGlobal: boolean, projectDir?: string): Promise<{
1714
- removed: string[];
1715
- errors: string[];
1716
- }>;
1717
- /**
1718
- * List all skills installed in the canonical skills directory.
1719
- *
1720
- * @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
- * ```
1731
- *
1732
- * @public
1733
- */
1734
- declare function listCanonicalSkills(): Promise<string[]>;
1735
-
1736
1490
  /**
1737
1491
  * Three-tier scope helper for Pi harness operations.
1738
1492
  *
@@ -1816,6 +1570,72 @@ type HarnessScope = {
1816
1570
  kind: 'project';
1817
1571
  projectDir: string;
1818
1572
  };
1573
+ /**
1574
+ * Options accepted by `resolveDefaultTargetProviders` (defined in
1575
+ * `./index.ts`).
1576
+ *
1577
+ * @remarks
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.
1581
+ *
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.
1585
+ *
1586
+ * @public
1587
+ */
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
+ }
1608
+ /**
1609
+ * Controls how `resolveDefaultTargetProviders` (defined in `./index.ts`)
1610
+ * selects target providers at runtime invocation time.
1611
+ *
1612
+ * @remarks
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.
1616
+ *
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.
1635
+ *
1636
+ * @public
1637
+ */
1638
+ type ExclusivityMode = 'auto' | 'force-pi' | 'legacy';
1819
1639
  /**
1820
1640
  * Metadata describing a Pi extension discovered on disk.
1821
1641
  *
@@ -1896,6 +1716,121 @@ interface HarnessInstallOptions {
1896
1716
  */
1897
1717
  force?: boolean;
1898
1718
  }
1719
+ /**
1720
+ * Counts of top-level CANT sections discovered in a `.cant` file.
1721
+ *
1722
+ * @remarks
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.
1728
+ *
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.
1736
+ *
1737
+ * @public
1738
+ */
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
+ }
1758
+ /**
1759
+ * Metadata describing a `.cant` profile installed at one of the three
1760
+ * Pi harness tiers.
1761
+ *
1762
+ * @remarks
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.
1768
+ *
1769
+ * @public
1770
+ */
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
+ }
1788
+ /**
1789
+ * Diagnostic emitted by the cant-core 42-rule validator, normalised to
1790
+ * the harness layer's vocabulary.
1791
+ *
1792
+ * @remarks
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.
1797
+ *
1798
+ * @public
1799
+ */
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
+ }
1812
+ /**
1813
+ * Result of validating a `.cant` profile via the cant-core 42-rule
1814
+ * engine.
1815
+ *
1816
+ * @remarks
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.
1823
+ *
1824
+ * @public
1825
+ */
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
+ }
1899
1834
  /**
1900
1835
  * Summary header extracted from the first line of a Pi session JSONL file.
1901
1836
  *
@@ -2058,6 +1993,12 @@ interface ModelListEntry {
2058
1993
  * is a routing hint passed to the harness; concrete harnesses may use it
2059
1994
  * to select an inner agent or simply record it for observability.
2060
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
+ *
2061
2002
  * @public
2062
2003
  */
2063
2004
  interface SubagentTask {
@@ -2065,6 +2006,42 @@ interface SubagentTask {
2065
2006
  targetProviderId: string;
2066
2007
  /** The prompt / instruction to give the spawned agent. */
2067
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;
2068
2045
  /**
2069
2046
  * Working directory for the spawned agent.
2070
2047
  * @defaultValue undefined
@@ -2076,18 +2053,136 @@ interface SubagentTask {
2076
2053
  */
2077
2054
  env?: Record<string, string>;
2078
2055
  /**
2079
- * 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.
2080
2058
  * @defaultValue undefined
2081
2059
  */
2082
2060
  signal?: AbortSignal;
2083
2061
  }
2084
2062
  /**
2085
- * Final result of a subagent's execution.
2063
+ * Per-call options that override harness-wide spawn defaults.
2086
2064
  *
2087
2065
  * @remarks
2088
- * Collected once the child process exits. {@link parsed} is populated
2089
- * on a best-effort basis: if the subagent emits JSON on stdout the
2090
- * 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.
2091
2186
  *
2092
2187
  * @public
2093
2188
  */
@@ -2110,18 +2205,87 @@ interface SubagentResult {
2110
2205
  *
2111
2206
  * @remarks
2112
2207
  * Returned synchronously from {@link Harness.spawnSubagent}. The caller
2113
- * may await {@link result} to collect the final output, or invoke
2114
- * {@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.
2115
2220
  *
2116
2221
  * @public
2117
2222
  */
2118
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;
2119
2238
  /** PID of the spawned process, or `null` if spawning did not yield one. */
2120
2239
  pid: number | null;
2121
- /** 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
+ */
2122
2258
  result: Promise<SubagentResult>;
2123
- /** 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
+ */
2124
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[];
2125
2289
  }
2126
2290
  /**
2127
2291
  * Contract every first-class harness must implement.
@@ -2209,16 +2373,25 @@ interface Harness {
2209
2373
  * @remarks
2210
2374
  * For Pi: invokes `child_process.spawn` with the provider's configured
2211
2375
  * `capabilities.spawn.spawnCommand`, appending the task prompt as a
2212
- * trailing positional argument. The returned handle lets the caller await
2213
- * 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.
2214
2386
  *
2215
2387
  * Optional — harnesses that cannot spawn other agents should omit this
2216
2388
  * method. Callers MUST feature-check before invoking.
2217
2389
  *
2218
2390
  * @param task - Subagent task specification.
2391
+ * @param opts - Per-call streaming and cleanup overrides.
2219
2392
  * @returns A live subagent handle.
2220
2393
  */
2221
- spawnSubagent?(task: SubagentTask): Promise<SubagentHandle>;
2394
+ spawnSubagent?(task: SubagentTask, opts?: SubagentSpawnOptions): Promise<SubagentHandle>;
2222
2395
  /**
2223
2396
  * Configure which models are available in the harness's model picker.
2224
2397
  *
@@ -2392,7 +2565,505 @@ interface Harness {
2392
2565
  listThemes?(projectDir?: string): Promise<ThemeEntry[]>;
2393
2566
  /** Remove a Pi theme by name from the given tier. */
2394
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);
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
+ * @example
2811
+ * ```typescript
2812
+ * setExclusivityMode("force-pi");
2813
+ * // ...run test...
2814
+ * resetExclusivityModeOverride();
2815
+ * // getExclusivityMode() now reads CAAMP_EXCLUSIVITY_MODE again
2816
+ * ```
2817
+ *
2818
+ * @public
2819
+ */
2820
+ declare function resetExclusivityModeOverride(): void;
2821
+
2822
+ /**
2823
+ * Format utility functions
2824
+ */
2825
+ /**
2826
+ * Deep merge two objects, with `source` values winning on conflict.
2827
+ *
2828
+ * Recursively merges nested plain objects. Arrays and non-object values from
2829
+ * `source` overwrite `target` values.
2830
+ *
2831
+ * @param target - Base object to merge into
2832
+ * @param source - Object with values that take precedence
2833
+ * @returns A new merged object (does not mutate inputs)
2834
+ *
2835
+ * @remarks
2836
+ * Only plain objects are recursively merged. Arrays and primitive values from
2837
+ * `source` replace `target` values outright. Neither input is mutated.
2838
+ *
2839
+ * @example
2840
+ * ```typescript
2841
+ * const merged = deepMerge({ a: 1, b: { c: 2 } }, { b: { d: 3 } });
2842
+ * // { a: 1, b: { c: 2, d: 3 } }
2843
+ * ```
2844
+ *
2845
+ * @public
2846
+ */
2847
+ declare function deepMerge(target: Record<string, unknown>, source: Record<string, unknown>): Record<string, unknown>;
2848
+ /**
2849
+ * Get a nested value from an object using a dot-notation key path.
2850
+ *
2851
+ * @param obj - Object to traverse
2852
+ * @param keyPath - Dot-separated key path (e.g. `"mcpServers"` or `"a.b.c"`)
2853
+ * @returns The value at the key path, or `undefined` if not found
2854
+ *
2855
+ * @remarks
2856
+ * Splits the key path on `.` and walks the object tree. Returns `undefined`
2857
+ * at the first missing or non-object segment.
2858
+ *
2859
+ * @example
2860
+ * ```typescript
2861
+ * getNestedValue({ a: { b: { c: 42 } } }, "a.b.c"); // 42
2862
+ * getNestedValue({ a: 1 }, "a.b"); // undefined
2863
+ * ```
2864
+ *
2865
+ * @public
2866
+ */
2867
+ declare function getNestedValue(obj: Record<string, unknown>, keyPath: string): unknown;
2868
+ /**
2869
+ * Ensure that the parent directories of a file path exist.
2870
+ *
2871
+ * Creates directories recursively if they do not exist.
2872
+ *
2873
+ * @param filePath - Absolute path to a file (parent directories will be created)
2874
+ *
2875
+ * @remarks
2876
+ * Uses `mkdir` with `recursive: true` so existing directories are not an error.
2877
+ *
2878
+ * @example
2879
+ * ```typescript
2880
+ * await ensureDir("/path/to/new/dir/file.json");
2881
+ * // /path/to/new/dir/ now exists
2882
+ * ```
2883
+ *
2884
+ * @public
2885
+ */
2886
+ declare function ensureDir(filePath: string): Promise<void>;
2887
+
2888
+ /**
2889
+ * Provides format-agnostic config read, write, and remove operations that
2890
+ * dispatch to JSON/JSONC, YAML, or TOML handlers based on the specified
2891
+ * format.
2892
+ *
2893
+ * @packageDocumentation
2894
+ */
2895
+
2896
+ /**
2897
+ * Read and parse a config file in the specified format.
2898
+ *
2899
+ * Dispatches to the appropriate format handler (JSON/JSONC, YAML, or TOML).
2900
+ *
2901
+ * @param filePath - Absolute path to the config file
2902
+ * @param format - Config file format
2903
+ * @returns Parsed config object
2904
+ * @throws If the file cannot be read or the format is unsupported
2905
+ *
2906
+ * @remarks
2907
+ * Supported formats: `"json"`, `"jsonc"`, `"yaml"`, `"toml"`. Throws for
2908
+ * any unrecognized format string.
2909
+ *
2910
+ * @example
2911
+ * ```typescript
2912
+ * const config = await readConfig("/path/to/config.json", "jsonc");
2913
+ * ```
2914
+ *
2915
+ * @public
2916
+ */
2917
+ declare function readConfig(filePath: string, format: ConfigFormat): Promise<Record<string, unknown>>;
2918
+ /**
2919
+ * Write a server entry to a config file, preserving existing content.
2920
+ *
2921
+ * Dispatches to the appropriate format handler. For JSONC files, comments are
2922
+ * preserved using `jsonc-parser`.
2923
+ *
2924
+ * @param filePath - Absolute path to the config file
2925
+ * @param format - Config file format
2926
+ * @param key - Dot-notation key path to the servers section (e.g. `"mcpServers"`)
2927
+ * @param serverName - Name/key for the server entry
2928
+ * @param serverConfig - Server configuration object to write
2929
+ * @throws If the format is unsupported
2930
+ *
2931
+ * @remarks
2932
+ * For JSONC files, comments and formatting are preserved using `jsonc-parser`.
2933
+ * For YAML and TOML, the file is fully re-serialized after deep-merging.
2934
+ *
2935
+ * @example
2936
+ * ```typescript
2937
+ * await writeConfig("/path/to/config.json", "jsonc", "mcpServers", "my-server", config);
2938
+ * ```
2939
+ *
2940
+ * @public
2941
+ */
2942
+ declare function writeConfig(filePath: string, format: ConfigFormat, key: string, serverName: string, serverConfig: unknown): Promise<void>;
2943
+ /**
2944
+ * Remove a server entry from a config file in the specified format.
2945
+ *
2946
+ * @param filePath - Absolute path to the config file
2947
+ * @param format - Config file format
2948
+ * @param key - Dot-notation key path to the servers section
2949
+ * @param serverName - Name/key of the server entry to remove
2950
+ * @returns `true` if the entry was removed, `false` otherwise
2951
+ * @throws If the format is unsupported
2952
+ *
2953
+ * @remarks
2954
+ * Delegates to the format-specific removal function. Returns `false` when the
2955
+ * file does not exist or the entry is not found.
2956
+ *
2957
+ * @example
2958
+ * ```typescript
2959
+ * const removed = await removeConfig("/path/to/config.json", "jsonc", "mcpServers", "my-server");
2960
+ * ```
2961
+ *
2962
+ * @public
2963
+ */
2964
+ declare function removeConfig(filePath: string, format: ConfigFormat, key: string, serverName: string): Promise<boolean>;
2965
+
2966
+ /**
2967
+ * Skill installer - canonical + symlink model
2968
+ *
2969
+ * Skills are stored once in a canonical location (.agents/skills/<name>/)
2970
+ * and symlinked to each target agent's skills directory.
2971
+ */
2972
+
2973
+ /**
2974
+ * Result of installing a skill to the canonical location and linking to agents.
2975
+ *
2976
+ * @example
2977
+ * ```typescript
2978
+ * const result = await installSkill(sourcePath, "my-skill", providers, true);
2979
+ * if (result.success) {
2980
+ * console.log(`Installed to ${result.canonicalPath}`);
2981
+ * console.log(`Linked to: ${result.linkedAgents.join(", ")}`);
2982
+ * }
2983
+ * ```
2984
+ *
2985
+ * @public
2986
+ */
2987
+ interface SkillInstallResult {
2988
+ /** Skill name. */
2989
+ name: string;
2990
+ /** Absolute path to the canonical installation directory. */
2991
+ canonicalPath: string;
2992
+ /** Provider IDs that were successfully linked. */
2993
+ linkedAgents: string[];
2994
+ /** Error messages from failed link operations. */
2995
+ errors: string[];
2996
+ /** Whether at least one agent was successfully linked. */
2997
+ success: boolean;
2395
2998
  }
2999
+ /**
3000
+ * Install a skill from a local path to the canonical location and link to agents.
3001
+ *
3002
+ * @remarks
3003
+ * Copies the skill directory to the canonical skills directory and creates symlinks
3004
+ * (or copies on Windows) from each provider's skills directory to the canonical path.
3005
+ *
3006
+ * @param sourcePath - Local path to the skill directory to install
3007
+ * @param skillName - Name for the installed skill
3008
+ * @param providers - Target providers to link the skill to
3009
+ * @param isGlobal - Whether to link to global or project skill directories
3010
+ * @param projectDir - Project directory (defaults to `process.cwd()`)
3011
+ * @returns Install result with linked agents and any errors
3012
+ *
3013
+ * @example
3014
+ * ```typescript
3015
+ * const result = await installSkill("/tmp/my-skill", "my-skill", providers, true, "/my/project");
3016
+ * if (result.success) {
3017
+ * console.log(`Linked to: ${result.linkedAgents.join(", ")}`);
3018
+ * }
3019
+ * ```
3020
+ *
3021
+ * @public
3022
+ */
3023
+ declare function installSkill(sourcePath: string, skillName: string, providers: Provider[], isGlobal: boolean, projectDir?: string): Promise<SkillInstallResult>;
3024
+ /**
3025
+ * Remove a skill from the canonical location and all agent symlinks.
3026
+ *
3027
+ * @remarks
3028
+ * Removes symlinks from each provider's skills directory and then removes the
3029
+ * canonical copy from the centralized canonical skills directory.
3030
+ *
3031
+ * @param skillName - Name of the skill to remove
3032
+ * @param providers - Providers to unlink the skill from
3033
+ * @param isGlobal - Whether to target global or project skill directories
3034
+ * @param projectDir - Project directory (defaults to `process.cwd()`)
3035
+ * @returns Object with arrays of successfully removed provider IDs and error messages
3036
+ *
3037
+ * @example
3038
+ * ```typescript
3039
+ * const { removed, errors } = await removeSkill("my-skill", providers, true, "/my/project");
3040
+ * console.log(`Removed from: ${removed.join(", ")}`);
3041
+ * ```
3042
+ *
3043
+ * @public
3044
+ */
3045
+ declare function removeSkill(skillName: string, providers: Provider[], isGlobal: boolean, projectDir?: string): Promise<{
3046
+ removed: string[];
3047
+ errors: string[];
3048
+ }>;
3049
+ /**
3050
+ * List all skills installed in the canonical skills directory.
3051
+ *
3052
+ * @remarks
3053
+ * Returns the directory names of all skills, which correspond to skill names.
3054
+ * Includes both regular directories and symlinks in the canonical location.
3055
+ *
3056
+ * @returns Array of skill names
3057
+ *
3058
+ * @example
3059
+ * ```typescript
3060
+ * const skills = await listCanonicalSkills();
3061
+ * // ["my-skill", "another-skill"]
3062
+ * ```
3063
+ *
3064
+ * @public
3065
+ */
3066
+ declare function listCanonicalSkills(): Promise<string[]>;
2396
3067
 
2397
3068
  /**
2398
3069
  * Pi coding agent harness.
@@ -2459,35 +3130,154 @@ declare class PiHarness implements Harness {
2459
3130
  * auto-discovering `AGENTS.md` from the working directory upwards.
2460
3131
  */
2461
3132
  private agentsMdPath;
2462
- /** {@inheritDoc Harness.installSkill} */
3133
+ /**
3134
+ * Install a skill directory into the resolved Pi skills location.
3135
+ */
2463
3136
  installSkill(sourcePath: string, skillName: string, scope: HarnessScope): Promise<void>;
2464
- /** {@inheritDoc Harness.removeSkill} */
3137
+ /**
3138
+ * Remove a skill directory from the resolved Pi skills location.
3139
+ */
2465
3140
  removeSkill(skillName: string, scope: HarnessScope): Promise<void>;
2466
- /** {@inheritDoc Harness.listSkills} */
3141
+ /**
3142
+ * List the installed skill directories at the given scope.
3143
+ */
2467
3144
  listSkills(scope: HarnessScope): Promise<string[]>;
2468
- /** {@inheritDoc Harness.injectInstructions} */
3145
+ /**
3146
+ * Inject or replace a CAAMP-managed instruction block inside the Pi
3147
+ * `AGENTS.md` file for the resolved scope.
3148
+ */
2469
3149
  injectInstructions(content: string, scope: HarnessScope): Promise<void>;
2470
- /** {@inheritDoc Harness.removeInstructions} */
3150
+ /**
3151
+ * Remove the CAAMP-managed instruction block from the Pi `AGENTS.md`
3152
+ * file at the resolved scope.
3153
+ */
2471
3154
  removeInstructions(scope: HarnessScope): Promise<void>;
2472
3155
  /**
2473
- * {@inheritDoc Harness.spawnSubagent}
3156
+ * Spawn a subagent through Pi's configured `spawnCommand` and return a
3157
+ * live handle bound to the canonical streaming, attribution, and
3158
+ * cleanup contract.
3159
+ *
3160
+ * @remarks
3161
+ * Per ADR-035 §D6 this is the **only** sanctioned subagent spawn path
3162
+ * in CLEO. All historical direct `child_process.spawn` callers in
3163
+ * subagent contexts (including the `cant-bridge.ts` Pi extension and
3164
+ * the legacy CLEO orchestrator paths) MUST migrate to this method so
3165
+ * the contract below holds uniformly. A custom biome rule banning
3166
+ * raw `spawn()` from subagent code is planned for v3 cleanup but is
3167
+ * intentionally NOT enforced in v2 to keep the migration incremental.
3168
+ *
3169
+ * **Streaming semantics** — Pi's `--mode json` produces line-delimited
3170
+ * JSON on stdout. The harness:
3171
+ *
3172
+ * - Line-buffers stdout, parses each line as JSON, and forwards a
3173
+ * `{ kind: 'message', subagentId, lineNumber, payload }`
3174
+ * {@link SubagentStreamEvent} via {@link SubagentSpawnOptions.onStream}.
3175
+ * Non-parseable lines increment a warning counter (recorded in the
3176
+ * child session as `{ type: 'raw' }`) but never crash the loop.
3177
+ * - Line-buffers stderr separately, forwards each line as
3178
+ * `{ kind: 'stderr', subagentId, payload: { line } }`, and stores
3179
+ * it in a 100-line ring buffer accessible via
3180
+ * {@link SubagentHandle.recentStderr}. Stderr is **never** injected
3181
+ * into the parent LLM context per ADR-035 §D6.
3182
+ * - Emits a final `{ kind: 'exit', subagentId, payload: SubagentExitResult }`
3183
+ * when the child terminates.
3184
+ *
3185
+ * **Session attribution** — Every spawn produces a child session JSONL
3186
+ * file at
3187
+ * `~/.pi/agent/sessions/subagents/subagent-{parentSessionId}-{taskId}.jsonl`.
3188
+ * The header line records the subagentId, taskId, and parent linkage.
3189
+ * When {@link SubagentTask.parentSessionPath} is supplied, a
3190
+ * {@link SubagentLinkEntry} is appended to the parent session file as
3191
+ * a JSONL line so listing the parent surfaces its children.
3192
+ *
3193
+ * **Exit propagation** — {@link SubagentHandle.exitPromise} resolves
3194
+ * with `{ code, signal, childSessionPath, durationMs }` exactly once
3195
+ * when the child exits. The promise NEVER rejects: failure is
3196
+ * encoded by a non-zero `code`, a non-null `signal`, or partial
3197
+ * output preserved in the child session file.
3198
+ *
3199
+ * **Cleanup** — {@link SubagentHandle.terminate} sends SIGTERM, waits
3200
+ * the configured grace window, then sends SIGKILL if the child is
3201
+ * still alive. The grace window is sourced from
3202
+ * {@link SubagentSpawnOptions.terminateGraceMs} when supplied,
3203
+ * otherwise from `settings.json:pi.subagent.terminateGraceMs`,
3204
+ * otherwise from {@link DEFAULT_TERMINATE_GRACE_MS}. A
3205
+ * `subagent_exit` entry with reason `terminated` is appended to the
3206
+ * child session file when cleanup runs.
3207
+ *
3208
+ * **Concurrency** — Use the static helpers
3209
+ * {@link PiHarness.raceSubagents} and
3210
+ * {@link PiHarness.settleAllSubagents} to compose `parallel: race`
3211
+ * and `parallel: settle` constructs from CANT workflows over multiple
3212
+ * handles.
3213
+ *
3214
+ * **Orphan handling** — On the first spawn the harness registers a
3215
+ * process-wide `'exit'` handler that terminates every still-active
3216
+ * subagent so a parent crash never strands children.
3217
+ *
3218
+ * Throws immediately when the provider entry is missing a
3219
+ * `spawnCommand` so callers see configuration errors early rather
3220
+ * than at child-exit time.
3221
+ *
3222
+ * @param task - Subagent task specification.
3223
+ * @param opts - Per-call streaming and cleanup overrides.
3224
+ * @returns A live subagent handle.
3225
+ */
3226
+ spawnSubagent(task: SubagentTask, opts?: SubagentSpawnOptions): Promise<SubagentHandle>;
3227
+ /**
3228
+ * Race a set of subagent handles, returning the first one that exits.
2474
3229
  *
2475
3230
  * @remarks
2476
- * Invokes Pi's configured `spawnCommand` (e.g.
2477
- * `["pi", "--mode", "json", "-p", "--no-session"]`) with the task prompt
2478
- * appended as the trailing positional argument. The {@link SubagentTask.targetProviderId}
2479
- * is a routing hint carried in the prompt stream; Pi's own extension
2480
- * layer dispatches to the correct inner agent.
3231
+ * Maps CANT's `parallel: race` construct (per ADR-035 §D6) onto the
3232
+ * canonical {@link spawnSubagent} contract. The losing handles are
3233
+ * gracefully terminated via {@link SubagentHandle.terminate} once the
3234
+ * first settles so no straggler children outlive the race.
3235
+ *
3236
+ * @param handles - Subagent handles to race.
3237
+ * @returns The {@link SubagentExitResult} of the first child to exit.
3238
+ * @throws When `handles` is empty (caller bug — a race over zero
3239
+ * children has no winner).
3240
+ */
3241
+ static raceSubagents(handles: SubagentHandle[]): Promise<SubagentExitResult>;
3242
+ /**
3243
+ * Settle a set of subagent handles, returning a parallel array of
3244
+ * results.
3245
+ *
3246
+ * @remarks
3247
+ * Maps CANT's `parallel: settle` construct (per ADR-035 §D6) onto the
3248
+ * canonical {@link spawnSubagent} contract. Because
3249
+ * {@link SubagentHandle.exitPromise} never rejects, every entry in
3250
+ * the returned array is `{ status: 'fulfilled', value: ... }` under
3251
+ * normal operation; the `PromiseSettledResult` shape is preserved
3252
+ * for forward compatibility with future failure modes.
3253
+ *
3254
+ * @param handles - Subagent handles to settle.
3255
+ * @returns Parallel array of settled exit results, one per input.
3256
+ */
3257
+ static settleAllSubagents(handles: SubagentHandle[]): Promise<PromiseSettledResult<SubagentExitResult>[]>;
3258
+ /**
3259
+ * Per-line stdout dispatcher used by the streaming buffer flusher.
2481
3260
  *
2482
- * Throws immediately when the provider entry is missing a `spawnCommand`
2483
- * so callers see configuration errors early rather than at child-exit time.
3261
+ * @remarks
3262
+ * Extracted as a private method so the line-handling logic stays
3263
+ * close to {@link spawnSubagent} but does not bloat the parent
3264
+ * function. Skips empty lines (a leading newline produces a zero-
3265
+ * length entry that has no semantic meaning).
3266
+ */
3267
+ private handleStdoutLine;
3268
+ /**
3269
+ * Read the Pi `settings.json` file for the resolved scope.
2484
3270
  */
2485
- spawnSubagent(task: SubagentTask): Promise<SubagentHandle>;
2486
- /** {@inheritDoc Harness.readSettings} */
2487
3271
  readSettings(scope: HarnessScope): Promise<unknown>;
2488
- /** {@inheritDoc Harness.writeSettings} */
3272
+ /**
3273
+ * Merge a partial patch into the Pi `settings.json` file for the
3274
+ * resolved scope using an atomic write.
3275
+ */
2489
3276
  writeSettings(patch: Record<string, unknown>, scope: HarnessScope): Promise<void>;
2490
- /** {@inheritDoc Harness.configureModels} */
3277
+ /**
3278
+ * Persist the supplied model-name patterns into `settings.enabledModels`
3279
+ * at the resolved scope.
3280
+ */
2491
3281
  configureModels(modelPatterns: string[], scope: HarnessScope): Promise<void>;
2492
3282
  /**
2493
3283
  * Resolve the `models.json` path for a given legacy two-tier scope.
@@ -2504,45 +3294,128 @@ declare class PiHarness implements Harness {
2504
3294
  * the single authoritative location per ADR-035 §D2.
2505
3295
  */
2506
3296
  private sessionsDir;
2507
- /** {@inheritDoc Harness.installExtension} */
3297
+ /**
3298
+ * Install a Pi extension `.ts` source file into the resolved tier's
3299
+ * extensions directory, validating that it has a default export.
3300
+ */
2508
3301
  installExtension(sourcePath: string, name: string, tier: HarnessTier, projectDir?: string, opts?: HarnessInstallOptions): Promise<{
2509
3302
  targetPath: string;
2510
3303
  tier: HarnessTier;
2511
3304
  }>;
2512
- /** {@inheritDoc Harness.removeExtension} */
3305
+ /**
3306
+ * Remove a Pi extension `.ts` source file from the resolved tier.
3307
+ */
2513
3308
  removeExtension(name: string, tier: HarnessTier, projectDir?: string): Promise<boolean>;
2514
- /** {@inheritDoc Harness.listExtensions} */
3309
+ /**
3310
+ * List Pi extension files across every tier in precedence order,
3311
+ * flagging shadowed entries from lower tiers.
3312
+ */
2515
3313
  listExtensions(projectDir?: string): Promise<ExtensionEntry[]>;
2516
- /** {@inheritDoc Harness.listSessions} */
3314
+ /**
3315
+ * List Pi session JSONL files (including subagent children when
3316
+ * requested), summarising only the header line per file.
3317
+ */
2517
3318
  listSessions(opts?: {
2518
3319
  includeSubagents?: boolean;
2519
3320
  }): Promise<SessionSummary[]>;
2520
- /** {@inheritDoc Harness.showSession} */
3321
+ /**
3322
+ * Show the full entries of a single Pi session by id.
3323
+ */
2521
3324
  showSession(id: string): Promise<SessionDocument>;
2522
- /** {@inheritDoc Harness.readModelsConfig} */
3325
+ /**
3326
+ * Read the Pi `models.json` file for the resolved scope, tolerating
3327
+ * missing or malformed files by returning an empty provider map.
3328
+ */
2523
3329
  readModelsConfig(scope: HarnessScope): Promise<PiModelsConfig>;
2524
- /** {@inheritDoc Harness.writeModelsConfig} */
3330
+ /**
3331
+ * Write the Pi `models.json` file for the resolved scope via an atomic
3332
+ * tmp-then-rename sequence.
3333
+ */
2525
3334
  writeModelsConfig(config: PiModelsConfig, scope: HarnessScope): Promise<void>;
2526
- /** {@inheritDoc Harness.listModels} */
3335
+ /**
3336
+ * Compose a flat `ModelListEntry` list from `models.json` plus the
3337
+ * `enabledModels` and default-model hints in `settings.json`.
3338
+ */
2527
3339
  listModels(scope: HarnessScope): Promise<ModelListEntry[]>;
2528
- /** {@inheritDoc Harness.installPrompt} */
3340
+ /**
3341
+ * Install a Pi prompt directory (containing `prompt.md`) into the
3342
+ * resolved tier's prompts directory.
3343
+ */
2529
3344
  installPrompt(sourceDir: string, name: string, tier: HarnessTier, projectDir?: string, opts?: HarnessInstallOptions): Promise<{
2530
3345
  targetPath: string;
2531
3346
  tier: HarnessTier;
2532
3347
  }>;
2533
- /** {@inheritDoc Harness.listPrompts} */
3348
+ /**
3349
+ * List Pi prompt directories across every tier in precedence order,
3350
+ * flagging shadowed entries from lower tiers.
3351
+ */
2534
3352
  listPrompts(projectDir?: string): Promise<PromptEntry[]>;
2535
- /** {@inheritDoc Harness.removePrompt} */
3353
+ /**
3354
+ * Remove a Pi prompt directory from the resolved tier.
3355
+ */
2536
3356
  removePrompt(name: string, tier: HarnessTier, projectDir?: string): Promise<boolean>;
2537
- /** {@inheritDoc Harness.installTheme} */
3357
+ /**
3358
+ * Install a Pi theme file (`.ts`/`.tsx`/`.mts`/`.json`) into the
3359
+ * resolved tier's themes directory, blocking same-stem conflicts
3360
+ * unless `--force` is supplied.
3361
+ */
2538
3362
  installTheme(sourceFile: string, name: string, tier: HarnessTier, projectDir?: string, opts?: HarnessInstallOptions): Promise<{
2539
3363
  targetPath: string;
2540
3364
  tier: HarnessTier;
2541
3365
  }>;
2542
- /** {@inheritDoc Harness.listThemes} */
3366
+ /**
3367
+ * List Pi theme files across every tier in precedence order, flagging
3368
+ * shadowed entries from lower tiers.
3369
+ */
2543
3370
  listThemes(projectDir?: string): Promise<ThemeEntry[]>;
2544
- /** {@inheritDoc Harness.removeTheme} */
3371
+ /**
3372
+ * Remove a Pi theme from the resolved tier, matching any of the
3373
+ * supported theme extensions for the given name stem.
3374
+ */
2545
3375
  removeTheme(name: string, tier: HarnessTier, projectDir?: string): Promise<boolean>;
3376
+ /**
3377
+ * Install a `.cant` profile into the resolved tier after passing it
3378
+ * through the cant-core validator.
3379
+ *
3380
+ * Validates the source via {@link PiHarness.validateCantProfile}
3381
+ * before copying so we never persist a `.cant` file the runtime bridge
3382
+ * cannot load. The target layout is `<tier-root>/cant/<name>.cant`,
3383
+ * resolved through `resolveTierDir` so the project/user/global
3384
+ * hierarchy stays consistent with the other Wave-1 verbs.
3385
+ */
3386
+ installCantProfile(sourcePath: string, name: string, tier: HarnessTier, projectDir?: string, opts?: HarnessInstallOptions): Promise<{
3387
+ targetPath: string;
3388
+ tier: HarnessTier;
3389
+ counts: CantProfileCounts;
3390
+ }>;
3391
+ /**
3392
+ * Remove a `.cant` profile from the resolved tier.
3393
+ */
3394
+ removeCantProfile(name: string, tier: HarnessTier, projectDir?: string): Promise<boolean>;
3395
+ /**
3396
+ * List installed `.cant` profiles across every tier in precedence
3397
+ * order, parsing each file to extract section counts.
3398
+ *
3399
+ * Walks every tier in `TIER_PRECEDENCE` order, parsing each
3400
+ * discovered `.cant` file via cant-core to extract a
3401
+ * {@link CantProfileCounts} bag. Higher-precedence tiers shadow
3402
+ * lower-precedence entries with the same name; shadowed entries
3403
+ * still appear in the result but carry the
3404
+ * `shadowedByHigherTier` flag so callers can render the precedence
3405
+ * story without losing visibility of the duplicate.
3406
+ */
3407
+ listCantProfiles(projectDir?: string): Promise<CantProfileEntry[]>;
3408
+ /**
3409
+ * Validate a `.cant` source file against cant-core's parser and
3410
+ * 42-rule linter, returning section counts and per-diagnostic detail.
3411
+ *
3412
+ * Pure validator. Reads the file, runs `parseDocument` to derive
3413
+ * counts (when parsing succeeds) and `validateDocument` to collect
3414
+ * the 42-rule diagnostic feed. The two calls are kept independent so
3415
+ * we can still report counts for files that pass parsing but fail a
3416
+ * lint rule.
3417
+ */
3418
+ validateCantProfile(sourcePath: string): Promise<ValidateCantProfileResult>;
2546
3419
  }
2547
3420
 
2548
3421
  /**
@@ -2619,47 +3492,65 @@ declare function getPrimaryHarness(): Harness | null;
2619
3492
  * @returns Array of harness instances, one per provider that implements
2620
3493
  * the {@link Harness} contract.
2621
3494
  *
3495
+ * @example
3496
+ * ```typescript
3497
+ * for (const harness of getAllHarnesses()) {
3498
+ * console.log(harness.provider.id); // "pi", ...
3499
+ * }
3500
+ * ```
3501
+ *
2622
3502
  * @public
2623
3503
  */
2624
3504
  declare function getAllHarnesses(): Harness[];
2625
3505
  /**
2626
3506
  * Resolve the default set of target providers when the user has not passed
2627
- * `--agent`.
2628
- *
2629
- * @remarks
2630
- * Resolution policy:
2631
- *
2632
- * 1. If the registry's primary harness (the provider with
2633
- * `priority === "primary"`) is installed on the current system,
2634
- * return `[primaryProvider]` so that commands dispatch to the
2635
- * primary harness by default.
2636
- * 2. Otherwise, return the set of installed providers at priority
2637
- * `"primary"` or `"high"`. This restores the legacy "detected high-tier
2638
- * providers" fallback that CAAMP has always used when no primary
2639
- * harness is available.
2640
- * 3. If the priority filter yields an empty list (e.g. in tests that stub
2641
- * providers without a `priority` field, or in fresh installs with only
2642
- * medium/low-tier providers detected), fall back to the full installed
2643
- * provider list so that commands retain a valid target.
2644
- *
2645
- * This helper is intentionally defensive: it swallows registry-related
2646
- * exceptions (returning an empty-primary-harness result) so stubbed test
2647
- * environments that do not wire the full provider registry still behave
2648
- * sensibly.
2649
- *
3507
+ * `--agent`, honouring the active {@link ExclusivityMode}.
3508
+ *
3509
+ * @remarks
3510
+ * Resolution policy is layered. The active {@link ExclusivityMode} (read
3511
+ * via {@link getExclusivityMode}) selects which branch of the matrix runs:
3512
+ *
3513
+ * | Mode | Pi installed | Pi absent |
3514
+ * |---|---|---|
3515
+ * | `'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. |
3516
+ * | `'force-pi'` | Returns `[piProvider]`. | Throws {@link PiRequiredError}. |
3517
+ * | `'legacy'` | Returns the full installed provider list in priority order (matches pre-exclusivity behaviour). | Same. |
3518
+ *
3519
+ * **Install paths are unaffected.** Per ADR-035 §D7, this helper governs
3520
+ * RUNTIME INVOCATION dispatch only. Skill and instruction install
3521
+ * dispatchers ({@link dispatchInstallSkillAcrossProviders},
3522
+ * {@link dispatchRemoveSkillAcrossProviders}) intentionally do not call
3523
+ * this function they target every requested provider directly so that
3524
+ * users in `force-pi` mode can still run
3525
+ * `caamp skills install foo --agent claude-code` while Pi is being
3526
+ * installed.
3527
+ *
3528
+ * The helper is intentionally defensive: registry/detection exceptions
3529
+ * are caught and treated as "Pi unknown" so stubbed test environments
3530
+ * that do not wire the full registry still behave sensibly.
3531
+ *
3532
+ * @param options - Optional explicit provider selection (e.g. from
3533
+ * `--agent`) used by `auto`-mode deprecation warning detection. Omit to
3534
+ * request the implicit default resolution.
2650
3535
  * @returns Ordered list of providers to target by default.
3536
+ * @throws {@link PiRequiredError} when mode is `'force-pi'` and Pi is not
3537
+ * installed.
2651
3538
  *
2652
3539
  * @example
2653
3540
  * ```typescript
3541
+ * // Implicit default — used by `caamp skills list` and friends.
2654
3542
  * const targets = resolveDefaultTargetProviders();
2655
- * if (targets.length === 0) {
2656
- * console.error("No target providers found. Use --agent or --all.");
2657
- * }
3543
+ *
3544
+ * // Explicit user selection emits a deprecation warning in `auto` mode
3545
+ * // when the selection excludes Pi and Pi is installed.
3546
+ * const explicit = resolveDefaultTargetProviders({
3547
+ * explicit: [getProvider('claude-code')!],
3548
+ * });
2658
3549
  * ```
2659
3550
  *
2660
3551
  * @public
2661
3552
  */
2662
- declare function resolveDefaultTargetProviders(): Provider[];
3553
+ declare function resolveDefaultTargetProviders(options?: ResolveDefaultTargetProvidersOptions): Provider[];
2663
3554
 
2664
3555
  /**
2665
3556
  * All hook event categories (8 total).
@@ -4052,6 +4943,13 @@ interface McpDetectionEntry {
4052
4943
  * @param projectDir - Project directory used for the `project` scope.
4053
4944
  * @returns The absolute config file path, or `null` when unavailable.
4054
4945
  *
4946
+ * @example
4947
+ * ```typescript
4948
+ * const claudeCode = getProvider("claude-code")!;
4949
+ * const path = resolveMcpConfigPath(claudeCode, "project", "/tmp/app");
4950
+ * // e.g. "/tmp/app/.mcp.json"
4951
+ * ```
4952
+ *
4055
4953
  * @public
4056
4954
  */
4057
4955
  declare function resolveMcpConfigPath(provider: Provider, scope: McpScope, projectDir?: string): string | null;
@@ -4083,6 +4981,15 @@ declare function resolveMcpConfigPath(provider: Provider, scope: McpScope, proje
4083
4981
  * (defaults to `process.cwd()`).
4084
4982
  * @returns Array of MCP server entries, or `[]` when nothing was found.
4085
4983
  *
4984
+ * @example
4985
+ * ```typescript
4986
+ * const provider = getProvider("claude-code")!;
4987
+ * const entries = await listMcpServers(provider, "project");
4988
+ * for (const entry of entries) {
4989
+ * console.log(entry.name, entry.configPath);
4990
+ * }
4991
+ * ```
4992
+ *
4086
4993
  * @public
4087
4994
  */
4088
4995
  declare function listMcpServers(provider: Provider, scope: McpScope, projectDir?: string): Promise<McpServerEntry[]>;
@@ -4117,6 +5024,14 @@ type McpServerEntriesByProvider = Map<string, McpServerEntry[]>;
4117
5024
  * @param projectDir - Project directory used for the `project` scope.
4118
5025
  * @returns Map of provider id → server entries.
4119
5026
  *
5027
+ * @example
5028
+ * ```typescript
5029
+ * const byProvider = await listAllMcpServers("global");
5030
+ * for (const [providerId, entries] of byProvider) {
5031
+ * console.log(`${providerId}: ${entries.length} server(s)`);
5032
+ * }
5033
+ * ```
5034
+ *
4120
5035
  * @public
4121
5036
  */
4122
5037
  declare function listAllMcpServers(scope: McpScope, projectDir?: string): Promise<McpServerEntriesByProvider>;
@@ -4135,6 +5050,13 @@ declare function listAllMcpServers(scope: McpScope, projectDir?: string): Promis
4135
5050
  * @param projectDir - Project directory used for the `project` scope.
4136
5051
  * @returns Array of detection entries, one per MCP-capable provider.
4137
5052
  *
5053
+ * @example
5054
+ * ```typescript
5055
+ * const hits = await detectMcpInstallations("project");
5056
+ * const installed = hits.filter((h) => h.exists);
5057
+ * console.log(`MCP found on ${installed.length} providers`);
5058
+ * ```
5059
+ *
4138
5060
  * @public
4139
5061
  */
4140
5062
  declare function detectMcpInstallations(scope: McpScope, projectDir?: string): Promise<McpDetectionEntry[]>;
@@ -4225,6 +5147,18 @@ interface InstallMcpServerResult {
4225
5147
  * @throws `Error` when the provider has no MCP capability or no
4226
5148
  * project-scoped config path is available.
4227
5149
  *
5150
+ * @example
5151
+ * ```typescript
5152
+ * const provider = getProvider("claude-code")!;
5153
+ * const result = await installMcpServer(
5154
+ * provider,
5155
+ * "github",
5156
+ * { command: "npx", args: ["-y", "@modelcontextprotocol/server-github"] },
5157
+ * { scope: "project", force: false },
5158
+ * );
5159
+ * console.log(result.installed, result.conflicted);
5160
+ * ```
5161
+ *
4228
5162
  * @public
4229
5163
  */
4230
5164
  declare function installMcpServer(provider: Provider, serverName: string, config: McpServerConfig, opts: InstallMcpServerOptions): Promise<InstallMcpServerResult>;
@@ -4303,6 +5237,13 @@ interface RemoveMcpServerResult {
4303
5237
  * @param opts - Removal options.
4304
5238
  * @returns Structured result describing whether the entry was removed.
4305
5239
  *
5240
+ * @example
5241
+ * ```typescript
5242
+ * const provider = getProvider("claude-code")!;
5243
+ * const result = await removeMcpServer(provider, "my-server", { scope: "project" });
5244
+ * console.log(result.removed); // true | false
5245
+ * ```
5246
+ *
4306
5247
  * @public
4307
5248
  */
4308
5249
  declare function removeMcpServer(provider: Provider, serverName: string, opts: RemoveMcpServerOptions): Promise<RemoveMcpServerResult>;
@@ -4322,6 +5263,13 @@ declare function removeMcpServer(provider: Provider, serverName: string, opts: R
4322
5263
  * @param opts - Removal options applied uniformly to every provider.
4323
5264
  * @returns Array of per-provider removal results.
4324
5265
  *
5266
+ * @example
5267
+ * ```typescript
5268
+ * const results = await removeMcpServerFromAll("my-server", { scope: "global" });
5269
+ * const removed = results.filter((r) => r.removed);
5270
+ * console.log(`Removed from ${removed.length} providers`);
5271
+ * ```
5272
+ *
4325
5273
  * @public
4326
5274
  */
4327
5275
  declare function removeMcpServerFromAll(serverName: string, opts: RemoveMcpServerOptions): Promise<RemoveMcpServerResult[]>;
@@ -7099,4 +8047,4 @@ declare function parseSource(input: string): ParsedSource;
7099
8047
  */
7100
8048
  declare function isMarketplaceScoped(input: string): boolean;
7101
8049
 
7102
- 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 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, 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 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, 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, 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, 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, resolveAlias, resolveDefaultTargetProviders, resolveMcpConfigPath, resolveNativeEvent, resolveProviderSkillsDirs, resolveRegistryTemplatePath, scanDirectory, scanFile, scoreSkillRecommendation, searchSkills, selectProvidersByMinimumPriority, setQuiet, setVerbose, shouldOverrideSkill, supportsHook, toCanonical, toNative, toNativeBatch, toSarif, tokenizeCriteriaValue, translateToAll, updateInstructionsSingleOperation, validateInstructionIntegrity, validateRecommendationCriteria, validateSkill, writeConfig };
8050
+ 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 };