@ai-outfitter/outfitter 0.7.2 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/README.md +4 -2
  2. package/code/pi-extension/src/outfitter-extension.js +720 -0
  3. package/dist/agents/AgentAdapter.d.ts +2 -0
  4. package/dist/agents/AgentLaunch.js +5 -0
  5. package/dist/agents/AgentLaunch.js.map +1 -1
  6. package/dist/agents/OutfitterDocs.d.ts +2 -0
  7. package/dist/agents/OutfitterDocs.js +38 -0
  8. package/dist/agents/OutfitterDocs.js.map +1 -0
  9. package/dist/agents/pi/PiAdapter.js +15 -14
  10. package/dist/agents/pi/PiAdapter.js.map +1 -1
  11. package/dist/agents/pi/PiSkillSources.d.ts +8 -0
  12. package/dist/agents/pi/PiSkillSources.js +69 -0
  13. package/dist/agents/pi/PiSkillSources.js.map +1 -0
  14. package/dist/cli/OutfitterCli.js +7 -2
  15. package/dist/cli/OutfitterCli.js.map +1 -1
  16. package/dist/cli/commands/PiLoginLaunch.js +25 -728
  17. package/dist/cli/commands/PiLoginLaunch.js.map +1 -1
  18. package/dist/cli/commands/RunCommand.d.ts +4 -17
  19. package/dist/cli/commands/RunCommand.js +10 -146
  20. package/dist/cli/commands/RunCommand.js.map +1 -1
  21. package/dist/cli/commands/SetupCommand.d.ts +4 -63
  22. package/dist/cli/commands/SetupCommand.js +13 -673
  23. package/dist/cli/commands/SetupCommand.js.map +1 -1
  24. package/dist/cli/commands/run/RunLaunchSummary.d.ts +2 -0
  25. package/dist/cli/commands/run/RunLaunchSummary.js +35 -0
  26. package/dist/cli/commands/run/RunLaunchSummary.js.map +1 -0
  27. package/dist/cli/commands/run/RunProfileResolution.d.ts +37 -0
  28. package/dist/cli/commands/run/RunProfileResolution.js +115 -0
  29. package/dist/cli/commands/run/RunProfileResolution.js.map +1 -0
  30. package/dist/cli/commands/setup/SetupPrompts.d.ts +14 -0
  31. package/dist/cli/commands/setup/SetupPrompts.js +296 -0
  32. package/dist/cli/commands/setup/SetupPrompts.js.map +1 -0
  33. package/dist/cli/commands/setup/SetupSourceImport.d.ts +5 -0
  34. package/dist/cli/commands/setup/SetupSourceImport.js +177 -0
  35. package/dist/cli/commands/setup/SetupSourceImport.js.map +1 -0
  36. package/dist/cli/commands/setup/SetupSourceLaunch.d.ts +4 -0
  37. package/dist/cli/commands/setup/SetupSourceLaunch.js +65 -0
  38. package/dist/cli/commands/setup/SetupSourceLaunch.js.map +1 -0
  39. package/dist/cli/commands/setup/SetupStarterSource.d.ts +21 -0
  40. package/dist/cli/commands/setup/SetupStarterSource.js +133 -0
  41. package/dist/cli/commands/setup/SetupStarterSource.js.map +1 -0
  42. package/dist/cli/commands/setup/SetupTypes.d.ts +91 -0
  43. package/dist/cli/commands/setup/SetupTypes.js +26 -0
  44. package/dist/cli/commands/setup/SetupTypes.js.map +1 -0
  45. package/doc/architecture/state_writeback_strategy.md +350 -0
  46. package/doc/documentation/README.md +21 -0
  47. package/doc/documentation/cli.md +74 -0
  48. package/doc/documentation/concepts.md +54 -0
  49. package/doc/documentation/first-time-cli-agent-users.md +137 -0
  50. package/doc/documentation/getting-started.md +49 -0
  51. package/doc/documentation/iterating-on-profiles.md +109 -0
  52. package/doc/documentation/profile-repository.md +111 -0
  53. package/doc/documentation/profiles.md +183 -0
  54. package/doc/documentation/state.md +204 -0
  55. package/doc/documentation/support-matrix.md +46 -0
  56. package/doc/documentation/switching-to-outfitter.md +130 -0
  57. package/doc/documentation/usecases/engineering.md +114 -0
  58. package/doc/documentation/usecases/organization-profile-catalog.md +140 -0
  59. package/doc/documentation/usecases/persona-reviews.md +173 -0
  60. package/doc/philosophy.md +25 -0
  61. package/package.json +3 -2
@@ -0,0 +1,133 @@
1
+ // Resolves and validates setup starter sources: local .outfitter folders, remote git caches, and starter settings.
2
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
3
+ import { dirname, isAbsolute, join, resolve } from 'node:path';
4
+ import spawn from 'cross-spawn';
5
+ import { createRemoteRepositoryCachePath, normalizeGitUri, redactProfileSourceUriCredentials, } from '../../../profiles/ProfileCache.js';
6
+ import { isValidProfileId } from '../../../profiles/ProfileLoader.js';
7
+ import { createSettingsLoadPlan, loadSettingsFiles } from '../../../settings/SettingsLoader.js';
8
+ import { formatSettingsIssue, } from './SetupTypes.js';
9
+ export const prepareStarterLayout = (homeDirectory, projectDirectory, setupSourceUri, synchronizer = createGitSetupSourceSynchronizer()) => {
10
+ const localOutfitterPath = resolveLocalSetupSourceOutfitterPathFromUri(setupSourceUri, projectDirectory);
11
+ if (localOutfitterPath !== undefined) {
12
+ const settingsPath = join(localOutfitterPath, 'settings.yml');
13
+ validateStarterSettingsIfPresent(existsSync(settingsPath) ? settingsPath : undefined);
14
+ return {
15
+ cachePath: localOutfitterPath,
16
+ settingsPath: existsSync(settingsPath) ? settingsPath : undefined,
17
+ profilesPath: firstExistingPath(join(localOutfitterPath, 'profiles')),
18
+ sourceKind: 'local-live',
19
+ sourceOutfitterPath: localOutfitterPath,
20
+ };
21
+ }
22
+ const cachePath = createSetupSourceCachePath(homeDirectory, setupSourceUri);
23
+ synchronizer.sync(setupSourceUri, cachePath);
24
+ const settingsPath = firstExistingPath(join(cachePath, 'settings.yml'), join(cachePath, '.outfitter', 'settings.yml'));
25
+ validateStarterSettingsIfPresent(settingsPath);
26
+ const preferredProfilesPath = settingsPath?.endsWith(join('.outfitter', 'settings.yml'))
27
+ ? join(cachePath, '.outfitter', 'profiles')
28
+ : join(cachePath, 'profiles');
29
+ const profilesPath = firstExistingPath(preferredProfilesPath, join(cachePath, 'profiles'), join(cachePath, '.outfitter', 'profiles'));
30
+ return { cachePath, settingsPath, profilesPath, sourceKind: 'remote-cache' };
31
+ };
32
+ const createSetupSourceCachePath = (homeDirectory, setupSourceUri) => createRemoteRepositoryCachePath(homeDirectory, { uri: setupSourceUri });
33
+ const createGitSetupSourceSynchronizer = () => ({
34
+ sync(uri, cachePath) {
35
+ mkdirSync(dirname(cachePath), { recursive: true });
36
+ if (existsSync(cachePath)) {
37
+ runGit(['-C', cachePath, 'pull', '--ff-only'], uri);
38
+ return;
39
+ }
40
+ runGit(['clone', '--', normalizeGitUri(uri), cachePath], uri);
41
+ },
42
+ });
43
+ const runGit = (args, sensitiveUri) => {
44
+ const result = spawn.sync('git', args, { stdio: 'pipe', encoding: 'utf8' });
45
+ if (result.status !== 0) {
46
+ /* v8 ignore next -- the final fallback only applies if git emits no stdout or stderr. */
47
+ throw new Error(redactSensitiveText((result.stderr || result.stdout || `git ${args.join(' ')} failed`).trim(), sensitiveUri));
48
+ }
49
+ };
50
+ const redactSensitiveText = (message, uri) => message
51
+ .split(uri)
52
+ .join(redactProfileSourceUriCredentials(uri))
53
+ .split(normalizeGitUri(uri))
54
+ .join(redactProfileSourceUriCredentials(normalizeGitUri(uri)));
55
+ const firstExistingPath = (...paths) => paths.find((path) => existsSync(path));
56
+ export const validateStarterSettingsIfPresent = (settingsPath) => {
57
+ if (settingsPath === undefined) {
58
+ return;
59
+ }
60
+ const loaded = loadSettingsFiles(createSettingsLoadPlan([{ scope: 'user', path: settingsPath }]));
61
+ if (loaded.issues.length > 0) {
62
+ throw new Error(`Cannot setup from invalid starter settings: ${loaded.issues.map(formatSettingsIssue).join('; ')}`);
63
+ }
64
+ };
65
+ export const readStarterDefaultProfileId = (settingsPath) => {
66
+ if (settingsPath === undefined) {
67
+ return 'engineer';
68
+ }
69
+ const loaded = loadSettingsFiles(createSettingsLoadPlan([{ scope: 'user', path: settingsPath }]));
70
+ return loaded.files[0]?.settings.defaultProfile ?? 'engineer';
71
+ };
72
+ export const readStarterExplicitDefaultProfileId = (settingsPath) => {
73
+ if (settingsPath === undefined) {
74
+ return undefined;
75
+ }
76
+ const loaded = loadSettingsFiles(createSettingsLoadPlan([{ scope: 'user', path: settingsPath }]));
77
+ return loaded.files[0]?.settings.defaultProfile;
78
+ };
79
+ export const readUserDefaultProfileId = (files) => files.find((file) => file.location.scope === 'user')?.settings.defaultProfile ?? 'engineer';
80
+ export const ensureExistingUserSettingsDefaultProfile = (settingsPath, files, defaultProfileId) => {
81
+ const userSettings = files.find((file) => file.location.scope === 'user');
82
+ if (userSettings === undefined || userSettings.settings.defaultProfile !== undefined) {
83
+ return;
84
+ }
85
+ const content = readFileSync(settingsPath, 'utf8');
86
+ writeFileSync(settingsPath, `${content}\ndefault_profile: ${defaultProfileId}\n`);
87
+ };
88
+ export const assertValidDefaultProfileId = (profileId) => {
89
+ if (!isValidProfileId(profileId)) {
90
+ throw new Error(`Default profile '${profileId}' is not a filesystem-safe Outfitter profile id.`);
91
+ }
92
+ };
93
+ export const createDefaultSettingsContent = (defaultProfileId = 'engineer') => [
94
+ `default_profile: ${defaultProfileId}`,
95
+ 'profile_sources:',
96
+ ' - github: ai-outfitter/default-profiles',
97
+ ' path: profiles',
98
+ ' - path: ./profiles',
99
+ '',
100
+ ].join('\n');
101
+ export const createInitialSettingsIfMissing = (settingsPath, starterSettingsPath) => {
102
+ if (existsSync(settingsPath)) {
103
+ return false;
104
+ }
105
+ mkdirSync(dirname(settingsPath), { recursive: true });
106
+ writeFileSync(settingsPath, starterSettingsPath === undefined
107
+ ? createDefaultSettingsContent()
108
+ : readStarterSettingsContent(starterSettingsPath));
109
+ return true;
110
+ };
111
+ export const readStarterSettingsContent = (starterSettingsPath) => {
112
+ const content = readFileSync(starterSettingsPath, 'utf8');
113
+ const loaded = loadSettingsFiles(createSettingsLoadPlan([{ scope: 'user', path: starterSettingsPath }]));
114
+ if (loaded.files[0]?.settings.defaultProfile !== undefined) {
115
+ return content;
116
+ }
117
+ return `default_profile: engineer\n${content}`;
118
+ };
119
+ /* v8 ignore start -- local setup-source path probing is covered by filesystem integration tests. */
120
+ export const resolveLocalSetupSourceOutfitterPath = (input) => input.setupSourceUri === undefined
121
+ ? undefined
122
+ : resolveLocalSetupSourceOutfitterPathFromUri(input.setupSourceUri, input.projectDirectory);
123
+ export const resolveLocalSetupSourceOutfitterPathFromUri = (setupSourceUri, projectDirectory) => {
124
+ if (isRemoteSetupSourceUri(setupSourceUri)) {
125
+ return undefined;
126
+ }
127
+ const sourcePath = isAbsolute(setupSourceUri) ? setupSourceUri : resolve(projectDirectory, setupSourceUri);
128
+ const outfitterPath = sourcePath.endsWith('.outfitter') ? sourcePath : join(sourcePath, '.outfitter');
129
+ return existsSync(outfitterPath) ? outfitterPath : undefined;
130
+ };
131
+ const isRemoteSetupSourceUri = (source) => /^[a-z][a-z0-9+.-]*:/iu.test(source) && !isAbsolute(source);
132
+ /* v8 ignore stop */
133
+ //# sourceMappingURL=SetupStarterSource.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SetupStarterSource.js","sourceRoot":"","sources":["../../../../src/cli/commands/setup/SetupStarterSource.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,91 @@
1
+ import type { SyncCommandDependencies, SyncCommandResult } from '../SyncCommand.js';
2
+ import type { WelcomeCommandDependencies, WelcomeCommandResult } from '../WelcomeCommand.js';
3
+ export interface SetupCommandInput {
4
+ readonly homeDirectory: string;
5
+ readonly projectDirectory: string;
6
+ readonly setupSourceUri?: string;
7
+ }
8
+ export interface SetupCommandResult {
9
+ readonly settingsPath: string;
10
+ readonly defaultProfilePath: string;
11
+ readonly createdSettings: boolean;
12
+ readonly copiedStarterProfileFiles: number;
13
+ readonly createdDefaultProfile: boolean;
14
+ readonly syncResult: SyncCommandResult;
15
+ readonly welcomeResult?: WelcomeCommandResult;
16
+ readonly messages: readonly string[];
17
+ }
18
+ export interface SetupSourceSynchronizer {
19
+ sync(uri: string, cachePath: string): void;
20
+ }
21
+ export interface SetupSourceLaunchInput {
22
+ readonly homeDirectory: string;
23
+ readonly projectDirectory: string;
24
+ readonly profileId?: string;
25
+ }
26
+ export interface SetupPiOnboardingLaunchInput {
27
+ readonly homeDirectory: string;
28
+ readonly projectDirectory: string;
29
+ readonly setupSourceUri?: string;
30
+ }
31
+ export type SetupSourcePostImportAction = 'start' | 'exit';
32
+ export type SetupSourcePostImportLaunchTarget = 'selected' | 'default';
33
+ export type SetupCommandDependencies = SyncCommandDependencies & WelcomeCommandDependencies & {
34
+ readonly setupSourceSynchronizer?: SetupSourceSynchronizer;
35
+ readonly selectDefaultProfile?: (profiles: readonly SetupProfileChoice[], currentDefault: string) => Promise<string>;
36
+ readonly selectSetupSourceImportTarget?: (choices: readonly SetupSourceImportTargetChoice[], defaultTarget: SetupSourceImportTarget) => Promise<SetupSourceImportTarget>;
37
+ readonly selectSetupSourceLaunchAction?: (profileId: string, launchTarget: SetupSourcePostImportLaunchTarget) => Promise<SetupSourcePostImportAction>;
38
+ readonly selectSetupSourceImportMode?: (choices: readonly SetupSourceImportModeChoice[], defaultMode: SetupSourceImportMode) => Promise<SetupSourceImportMode>;
39
+ readonly launchSetupSourceProfile?: (input: SetupSourceLaunchInput) => Promise<void>;
40
+ readonly launchPiOnboarding?: (input: SetupPiOnboardingLaunchInput) => Promise<{
41
+ readonly exitCode: number;
42
+ }>;
43
+ readonly runWelcome?: (input: SetupCommandInput, dependencies: SetupCommandDependencies) => Promise<WelcomeCommandResult | undefined>;
44
+ };
45
+ export interface SetupProfileChoice {
46
+ readonly id: string;
47
+ readonly label?: string;
48
+ readonly description?: string;
49
+ }
50
+ export type SetupSourceImportTarget = 'home' | 'project';
51
+ export type SetupSourceImportMode = 'copy' | 'symlink';
52
+ export interface SetupSourceImportTargetChoice {
53
+ readonly target: SetupSourceImportTarget;
54
+ readonly label: string;
55
+ readonly description: string;
56
+ }
57
+ export interface SetupSourceImportModeChoice {
58
+ readonly mode: SetupSourceImportMode;
59
+ readonly label: string;
60
+ readonly description: string;
61
+ }
62
+ export declare const setupSourceImportModeChoices: readonly SetupSourceImportModeChoice[];
63
+ export declare const setupSourceImportTargetChoices: readonly SetupSourceImportTargetChoice[];
64
+ export interface StarterLayout {
65
+ readonly cachePath: string;
66
+ readonly settingsPath?: string;
67
+ readonly profilesPath?: string;
68
+ readonly sourceKind: 'local-live' | 'remote-cache';
69
+ readonly sourceOutfitterPath?: string;
70
+ }
71
+ export interface SetupSourceOnboardingResult {
72
+ readonly importTarget: SetupSourceImportTarget;
73
+ readonly selectedProfileId: string;
74
+ readonly importMode: SetupSourceImportMode;
75
+ }
76
+ export interface AppliedSetupSourceImport {
77
+ readonly settingsPath: string;
78
+ readonly settingsDescription: string;
79
+ readonly profilesPath: string;
80
+ readonly createdSettings: boolean;
81
+ readonly copiedStarterProfileFiles: number;
82
+ readonly copiedStarterResourceFiles: number;
83
+ readonly selectedProfileAlreadyExists: boolean;
84
+ readonly selectedProfileConflictMessage?: string;
85
+ readonly symlinkedOutfitter: boolean;
86
+ }
87
+ export declare const formatSettingsIssue: (issue: {
88
+ readonly filePath: string;
89
+ readonly path: string;
90
+ readonly message: string;
91
+ }) => string;
@@ -0,0 +1,26 @@
1
+ export const setupSourceImportModeChoices = [
2
+ {
3
+ mode: 'copy',
4
+ label: 'Copy snapshot',
5
+ description: 'copy profiles into the selected .outfitter folder; safest for normal use',
6
+ },
7
+ {
8
+ mode: 'symlink',
9
+ label: 'Symlink for development',
10
+ description: 'link the selected .outfitter folder to the local source so shared profile edits apply immediately',
11
+ },
12
+ ];
13
+ export const setupSourceImportTargetChoices = [
14
+ {
15
+ target: 'home',
16
+ label: 'User home',
17
+ description: 'install profiles into ~/.outfitter for all repositories on this machine',
18
+ },
19
+ {
20
+ target: 'project',
21
+ label: 'Current project',
22
+ description: 'install profiles into this project .outfitter folder only',
23
+ },
24
+ ];
25
+ export const formatSettingsIssue = (issue) => `${issue.filePath}#${issue.path} ${issue.message}`;
26
+ //# sourceMappingURL=SetupTypes.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SetupTypes.js","sourceRoot":"","sources":["../../../../src/cli/commands/setup/SetupTypes.ts"],"names":[],"mappings":"AAyFA,MAAM,CAAC,MAAM,4BAA4B,GAA2C;IAClF;QACE,IAAI,EAAE,MAAM;QACZ,KAAK,EAAE,eAAe;QACtB,WAAW,EAAE,0EAA0E;KACxF;IACD;QACE,IAAI,EAAE,SAAS;QACf,KAAK,EAAE,yBAAyB;QAChC,WAAW,EAAE,mGAAmG;KACjH;CACF,CAAC;AAEF,MAAM,CAAC,MAAM,8BAA8B,GAA6C;IACtF;QACE,MAAM,EAAE,MAAM;QACd,KAAK,EAAE,WAAW;QAClB,WAAW,EAAE,yEAAyE;KACvF;IACD;QACE,MAAM,EAAE,SAAS;QACjB,KAAK,EAAE,iBAAiB;QACxB,WAAW,EAAE,2DAA2D;KACzE;CACF,CAAC;AA4BF,MAAM,CAAC,MAAM,mBAAmB,GAAG,CAAC,KAInC,EAAU,EAAE,CAAC,GAAG,KAAK,CAAC,QAAQ,IAAI,KAAK,CAAC,IAAI,IAAI,KAAK,CAAC,OAAO,EAAE,CAAC"}
@@ -0,0 +1,350 @@
1
+ # State Writeback Strategy
2
+
3
+ This document describes Outfitter's current model for handling writes that agent CLIs make inside a composite profile.
4
+
5
+ A composite profile is temporary, but agent CLIs sometimes perform intentionally durable writes, such as logging in, installing plugins, changing settings, or updating MCP configuration.
6
+ Outfitter makes those paths explicit: adapter-declared writable paths are materialized with a resolved `state_persistence` strategy before the child CLI starts, and non-persistent or unknown writes are diagnosed after the child exits.
7
+
8
+ ## Functional model
9
+
10
+ Outfitter separates three kinds of files that may exist in a composite profile:
11
+
12
+ 1. **Generated runtime files**: files Outfitter assembles from settings, profiles, templates, and adapter rules.
13
+ Outfitter may regenerate these while the child agent is running when their source inputs change.
14
+ 2. **Declared state paths**: adapter-known files or directories the agent CLI may update intentionally, such as auth, settings, MCP config, plugins, caches, and sessions.
15
+ 3. **Unknown writes**: files or directories the agent creates outside the adapter-declared state paths.
16
+
17
+ Only declared state paths can be made durable automatically.
18
+ Unknown writes are never silently persisted because Outfitter does not know their intended owner, merge rules, or durable destination.
19
+ Generated runtime files and declared state paths are deliberately handled separately so live profile/template updates do not erase or re-baseline agent state changes made during the same run.
20
+
21
+ The user-facing state update lifecycle is:
22
+
23
+ 1. **Choose a profile**.
24
+ Profile resolution determines the effective `state_persistence` map using normal profile precedence.
25
+ 2. **Resolve adapter defaults**.
26
+ For each path the selected adapter declares, Outfitter uses the profile override when present and otherwise uses the adapter default.
27
+ 3. **Prepare the composite profile**.
28
+ Durable paths are connected to a profile-managed or native CLI location; non-durable paths are created as normal temporary composite profile paths.
29
+ 4. **Run the agent**.
30
+ The agent CLI reads and writes the composite profile as if it were its normal configuration directory.
31
+ 5. **Classify changes after exit**.
32
+ Outfitter checks non-durable declared paths and unknown paths and reports or fails according to their strategies.
33
+ 6. **Clean up temporary state**.
34
+ Temporary composite profile contents are discarded; durable symlink targets remain in their profile or native CLI location.
35
+
36
+ ## Current behavior
37
+
38
+ - Composite profiles remain temporary and reproducible by default.
39
+ - Outfitter does not do generic post-run copy-back or JSON/YAML merge-back.
40
+ - Persistent state is represented by symlinking a composite profile path to a profile file/directory or to the native CLI fallback path.
41
+ - Adapters may generate a concrete runtime file for a declared state path when they need deterministic launch-time reconciliation.
42
+ For example, the Pi adapter can generate a transformed `settings.json` that removes native `packages` entries already supplied by profile-controlled extensions, and then mark that declared path as `discard` for write detection during the run.
43
+ - Every adapter-declared state path has a resolved strategy before launch: profile overrides win, otherwise the adapter `default_strategy` is used, except for adapter-generated reconciliation files that are intentionally treated as discarded runtime files.
44
+ - Invalid or disallowed profile-requested `state_persistence` strategies fail before launch; adapter-internal reconciliation may still choose a one-run handling strategy for a generated runtime file.
45
+ - Non-persistent `warn` and `error` strategies are checked after the child CLI exits.
46
+ - Unknown writes outside adapter-declared paths are checked with the adapter's `unknown` pseudo-path strategy.
47
+ - `prompt` is reserved for a future interactive/control-plane workflow.
48
+ When accepted by a declaration today, it is treated as a non-persistent diagnostic like `warn`.
49
+
50
+ ## Non-goals
51
+
52
+ - Outfitter does not implement generic copy-back from the composite profile to profiles.
53
+ - Outfitter does not implement generic structured merge-back.
54
+ - Outfitter does not silently persist unknown writes.
55
+
56
+ ## Profile stack and native fallback
57
+
58
+ State persistence is a normal profile setting.
59
+ Its strategy overrides resolve through the same selected-profile stack as other profile data. When the user provides `--profile X`, only definitions of `X` and its explicit inheritance chain participate; the configured `default_profile` is selected only when no explicit profile is provided.
60
+
61
+ ```text
62
+ project-local definition of selected profile
63
+ project definition of selected profile
64
+ user definition of selected profile
65
+ URI/cache definitions of selected profile
66
+ explicit inheritance
67
+ Outfitter defaults
68
+ ```
69
+
70
+ Native CLI state is not represented as an extra profile layer.
71
+ For `symlink` paths without a profile-provided source, the selected adapter resolves a native fallback location directly, such as `~/.pi/agent/...` for most Pi state paths, `~/.claude/...` for most Claude Code state paths, or `<cache_directory>/utilities` for Pi `utilities/` and `bin/`.
72
+ This native fallback is not a base profile: it does not participate in profile inheritance or merge precedence, and it cannot contribute controls or profile YAML.
73
+ Claude Code `projects/` is additionally controlled by `controls.session_directory` or `controls.claude.session_directory` when set.
74
+
75
+ ## Path-keyed adapter declarations
76
+
77
+ Adapters declare writable state paths directly, using relative file paths as keys.
78
+ Directory paths use a trailing slash.
79
+ The same key is used for adapter coverage, `state_persistence` overrides, profile resource lookup, native fallback lookup, and composite profile materialization.
80
+
81
+ The Pi adapter currently declares:
82
+
83
+ ```yaml
84
+ state_paths:
85
+ auth.json:
86
+ default_strategy: symlink
87
+ allowed_strategies: [symlink, error, prompt]
88
+
89
+ settings.json:
90
+ default_strategy: symlink
91
+ allowed_strategies: [symlink, warn, error, prompt]
92
+ note: >-
93
+ When profile-controlled Pi extensions duplicate native settings packages,
94
+ Outfitter may generate a transformed runtime settings.json and treat this
95
+ declared path as discard for that launch. That discard handling is
96
+ adapter-internal; users still cannot request settings.json: discard
97
+ because discard is not listed in allowed_strategies.
98
+
99
+ keybindings.json:
100
+ default_strategy: symlink
101
+ allowed_strategies: [symlink, warn, error, prompt]
102
+ note: >-
103
+ Outfitter generates a transformed runtime keybindings.json so Shift+Tab
104
+ can switch Outfitter modes and Ctrl+Shift+T can cycle Pi thinking levels.
105
+ The generated runtime file is treated as discard for that launch.
106
+
107
+ mcp.json:
108
+ default_strategy: symlink
109
+ allowed_strategies: [symlink, warn, error, prompt]
110
+
111
+ models.json:
112
+ default_strategy: symlink
113
+ allowed_strategies: [symlink, warn, error, prompt]
114
+
115
+ trust.json:
116
+ default_strategy: symlink
117
+ allowed_strategies: [symlink, warn, error, prompt]
118
+
119
+ plugins/:
120
+ default_strategy: symlink
121
+ allowed_strategies: [symlink, discard, warn, error, prompt]
122
+
123
+ cache/:
124
+ default_strategy: symlink
125
+ allowed_strategies: [symlink, discard, warn, error]
126
+
127
+ sessions/:
128
+ default_strategy: symlink
129
+ allowed_strategies: [symlink, discard, warn, error]
130
+
131
+ npm/:
132
+ default_strategy: symlink
133
+ allowed_strategies: [symlink, discard, warn, error]
134
+
135
+ git/:
136
+ default_strategy: symlink
137
+ allowed_strategies: [symlink, discard, warn, error]
138
+
139
+ tmp/:
140
+ default_strategy: symlink
141
+ allowed_strategies: [symlink, discard]
142
+
143
+ utilities/:
144
+ default_strategy: symlink
145
+ allowed_strategies: [symlink, discard, warn, error]
146
+
147
+ bin/:
148
+ default_strategy: symlink
149
+ allowed_strategies: [symlink, discard, warn, error]
150
+
151
+ unknown:
152
+ default_strategy: warn
153
+ allowed_strategies: [discard, warn, error, prompt]
154
+ ```
155
+
156
+ The Claude Code adapter currently declares:
157
+
158
+ ```yaml
159
+ state_paths:
160
+ settings.json:
161
+ default_strategy: symlink
162
+ allowed_strategies: [symlink, warn, error, prompt]
163
+
164
+ agents/:
165
+ default_strategy: symlink
166
+ allowed_strategies: [symlink, discard, warn, error, prompt]
167
+
168
+ skills/:
169
+ default_strategy: symlink
170
+ allowed_strategies: [symlink, discard, warn, error, prompt]
171
+
172
+ commands/:
173
+ default_strategy: symlink
174
+ allowed_strategies: [symlink, discard, warn, error, prompt]
175
+
176
+ plugins/:
177
+ default_strategy: symlink
178
+ allowed_strategies: [symlink, discard, warn, error, prompt]
179
+
180
+ projects/:
181
+ default_strategy: symlink
182
+ allowed_strategies: [symlink, discard, warn, error]
183
+
184
+ debug/:
185
+ default_strategy: symlink
186
+ allowed_strategies: [symlink, discard, warn, error]
187
+
188
+ unknown:
189
+ default_strategy: warn
190
+ allowed_strategies: [discard, warn, error, prompt]
191
+ ```
192
+
193
+ ## Profile layout for state files
194
+
195
+ State files live under the relevant CLI-specific profile folder:
196
+
197
+ ```text
198
+ profiles/
199
+ default/
200
+ profile.yml
201
+ cli_specific/
202
+ pi/
203
+ auth.json
204
+ settings.json
205
+ keybindings.json
206
+ plugins/
207
+ claude/
208
+ settings.json
209
+ skills/
210
+ commands/
211
+ plugins/
212
+ ```
213
+
214
+ Except for special adapter paths described below, when a selected strategy is `symlink`, Outfitter searches the resolved profile folders from highest to lowest precedence for `cli_specific/<adapter>/<state-path>`.
215
+ If a profile contains the file or directory, Outfitter symlinks the composite profile path to that source.
216
+
217
+ For most Pi paths, if no profile source exists, Outfitter falls back to the corresponding native Pi agent path under `~/.pi/agent`.
218
+ Missing native fallback files/directories are created so the composite profile symlink has a durable destination.
219
+
220
+ Pi `utilities/` and `bin/` are special cache-backed paths: both resolve to `<cache_directory>/utilities` instead of profile or native Pi state.
221
+ This keeps pi-managed helper binaries reusable across temporary composite profiles without treating them as user-editable profile files.
222
+
223
+ For most Claude Code paths, if no profile source exists, Outfitter falls back to the corresponding native Claude Code path under `~/.claude`.
224
+ Claude Code `projects/` is special: `controls.claude.session_directory` overrides generic `controls.session_directory`, and the selected session directory becomes the `projects/` symlink source.
225
+ If neither session-directory control is present, `projects/` falls back to `~/.claude/projects`.
226
+
227
+ ## `state_persistence`
228
+
229
+ Profiles may override persistence by mapping adapter-declared paths to strategy names:
230
+
231
+ ```yaml
232
+ state_persistence:
233
+ auth.json: symlink
234
+ settings.json: symlink
235
+ plugins/: symlink
236
+ cache/: discard
237
+ sessions/: discard
238
+ unknown: warn
239
+ ```
240
+
241
+ The values are concrete strategy names.
242
+ `state_persistence` only needs overrides; omitted paths use the adapter declaration's `default_strategy`.
243
+
244
+ `state_persistence` is validated by the profile JSON Schema at read boundaries.
245
+ Outfitter also validates the resolved strategy against the adapter declaration before launch.
246
+
247
+ Functional examples:
248
+
249
+ ```yaml
250
+ # Persist logins and settings, but make caches and sessions run-local.
251
+ state_persistence:
252
+ auth.json: symlink
253
+ settings.json: symlink
254
+ cache/: discard
255
+ sessions/: discard
256
+ ```
257
+
258
+ ```yaml
259
+ # CI profile: fail if pi changes settings, MCP config, or unknown files.
260
+ state_persistence:
261
+ settings.json: error
262
+ mcp.json: error
263
+ plugins/: error
264
+ unknown: error
265
+ ```
266
+
267
+ ```yaml
268
+ # Exploratory profile: allow plugin experiments but report them after exit.
269
+ state_persistence:
270
+ plugins/: warn
271
+ unknown: warn
272
+ ```
273
+
274
+ ## Composite profile materialization
275
+
276
+ Before launch, Outfitter processes each adapter-declared state path:
277
+
278
+ 1. Resolve the path's strategy from profile `state_persistence` overrides, then the adapter `default_strategy`.
279
+ 2. Validate that the strategy is allowed for that path.
280
+ 3. Resolve a source path through the profile hierarchy when the strategy is `symlink`.
281
+ 4. Materialize the composite profile path.
282
+ 5. Record a baseline fingerprint for non-persistent and unknown write detection.
283
+
284
+ For `symlink`, Outfitter creates a symlink from the composite profile path to the resolved profile or native CLI source.
285
+
286
+ For `discard`, `warn`, `error`, and `prompt`, Outfitter creates normal temporary composite profile paths where needed and observes whether they changed.
287
+
288
+ ## Unknown writes
289
+
290
+ The `unknown` pseudo-path controls writes outside adapter-declared paths:
291
+
292
+ ```yaml
293
+ state_persistence:
294
+ unknown: warn
295
+ ```
296
+
297
+ Supported `unknown` strategies are non-persistent only:
298
+
299
+ - `discard`
300
+ - `warn`
301
+ - `error`
302
+ - `prompt`
303
+
304
+ `unknown` does not support `symlink`, because there is no declared durable destination.
305
+
306
+ ## Strategy selection guide
307
+
308
+ Use `symlink` when a write is part of durable agent setup, such as logging in, editing native settings, updating MCP config, or installing plugins that should be reused.
309
+ Use `discard` when the data is useful only during the current run, such as cache entries or throwaway sessions.
310
+ Use `warn` when mutation is acceptable but should be visible to the user.
311
+ Use `error` when mutation means the run was not reproducible enough, especially in CI or locked-down project profiles.
312
+ Use `prompt` only as a forward-compatible declaration for future interactive handling.
313
+
314
+ ## Strategies
315
+
316
+ ### `symlink`
317
+
318
+ Outfitter resolves the state path through the profile hierarchy, then the native CLI fallback, and symlinks that source into the composite profile.
319
+ Persistence happens because the CLI writes through the symlink to an intentional file or directory.
320
+
321
+ ### `discard`
322
+
323
+ Writes are allowed in the composite profile and are thrown away when the composite profile is deleted.
324
+ Outfitter does not emit diagnostics for changed `discard` paths.
325
+
326
+ ### `warn`
327
+
328
+ Writes are allowed, discarded, and reported after the child exits.
329
+ `--strict` makes these warnings fatal.
330
+
331
+ ### `error`
332
+
333
+ Writes are allowed during the child process but cause Outfitter to fail after the child exits if the path changed.
334
+ This is useful for CI and strict reproducibility.
335
+
336
+ ### `prompt`
337
+
338
+ `prompt` is reserved for a future interactive/control-plane workflow.
339
+ Current implementations that allow it treat writes as non-persistent diagnostics, equivalent to `warn`, with the strategy name preserved in the message.
340
+
341
+ ## Rationale
342
+
343
+ Path-keyed state declarations keep the model simple:
344
+
345
+ - the adapter declares the paths it knows the CLI may write and their default strategies;
346
+ - profiles may provide files at those same paths;
347
+ - the native fallback exposes native CLI files at those same paths;
348
+ - `state_persistence` says what to do with each path.
349
+
350
+ This avoids ambiguous writeback behavior and gives users a clear rule: if a CLI write should persist, configure that composite profile path as `symlink` and provide or accept the profile/native file that should receive the mutation.
@@ -0,0 +1,21 @@
1
+ # Outfitter documentation
2
+
3
+ User-facing Outfitter documentation.
4
+
5
+ - [Getting started](./getting-started.md)
6
+ - [Concepts](./concepts.md)
7
+ - [CLI reference](./cli.md)
8
+ - [First-time CLI agent users](./first-time-cli-agent-users.md)
9
+ - [Switching to Outfitter](./switching-to-outfitter.md)
10
+ - [Profiles](./profiles.md)
11
+ - [Profile repositories](./profile-repository.md)
12
+ - [Iterating on local and worktree profiles](./iterating-on-profiles.md)
13
+ - [Adapter support matrix](./support-matrix.md)
14
+ - [State persistence](./state.md)
15
+ - [Philosophy](../philosophy.md)
16
+
17
+ ## Use cases
18
+
19
+ - [Organization profile catalog](./usecases/organization-profile-catalog.md) — Publish shared team roles so new users can start with organization-approved defaults.
20
+ - [Engineering profile catalog](./usecases/engineering.md) — Package coding, platform, and review profiles for repeatable engineering workflows.
21
+ - [Persona reviews](./usecases/persona-reviews.md) — Create customer personas to get feedback on ideas, documentation, and designs.