@ai-outfitter/outfitter 0.9.0 → 0.10.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.
- package/.outfitter/skills/outfitter/SKILL.md +86 -0
- package/README.md +2 -0
- package/code/pi-extension/src/outfitter-extension.js +11 -0
- package/dist/agents/AdapterProfileControls.js +2 -2
- package/dist/agents/AdapterProfileControls.js.map +1 -1
- package/dist/agents/AgentAdapter.d.ts +0 -2
- package/dist/agents/LaunchResources.d.ts +4 -0
- package/dist/agents/LaunchResources.js +27 -1
- package/dist/agents/LaunchResources.js.map +1 -1
- package/dist/agents/OutfitterSkill.d.ts +11 -0
- package/dist/agents/OutfitterSkill.js +128 -0
- package/dist/agents/OutfitterSkill.js.map +1 -0
- package/dist/agents/claude/ClaudeAdapter.js +16 -1
- package/dist/agents/claude/ClaudeAdapter.js.map +1 -1
- package/dist/agents/pi/PiAdapter.js +4 -12
- package/dist/agents/pi/PiAdapter.js.map +1 -1
- package/dist/agents/pi/PiArgs.js +1 -1
- package/dist/agents/pi/PiArgs.js.map +1 -1
- package/dist/agents/pi/PiSkillSources.js +5 -1
- package/dist/agents/pi/PiSkillSources.js.map +1 -1
- package/dist/cli/OutfitterCli.js +0 -2
- package/dist/cli/OutfitterCli.js.map +1 -1
- package/dist/cli/commands/PiLoginLaunch.d.ts +5 -0
- package/dist/cli/commands/PiLoginLaunch.js +2 -0
- package/dist/cli/commands/PiLoginLaunch.js.map +1 -1
- package/dist/cli/commands/RunCommand.d.ts +0 -2
- package/dist/cli/commands/RunCommand.js +29 -9
- package/dist/cli/commands/RunCommand.js.map +1 -1
- package/dist/cli/commands/profile/LintCommand.js +33 -1
- package/dist/cli/commands/profile/LintCommand.js.map +1 -1
- package/dist/cli/commands/run/RunProfileResolution.d.ts +2 -0
- package/dist/cli/commands/run/RunProfileResolution.js +4 -0
- package/dist/cli/commands/run/RunProfileResolution.js.map +1 -1
- package/dist/profiles/Profile.d.ts +14 -1
- package/dist/profiles/Profile.js.map +1 -1
- package/dist/profiles/ProfileLoader.js +18 -2
- package/dist/profiles/ProfileLoader.js.map +1 -1
- package/dist/profiles/ProfileMerger.js +3 -2
- package/dist/profiles/ProfileMerger.js.map +1 -1
- package/dist/schemas/profile.schema.json +40 -3
- package/dist/skills/ProfileSkillResolution.d.ts +21 -0
- package/dist/skills/ProfileSkillResolution.js +88 -0
- package/dist/skills/ProfileSkillResolution.js.map +1 -0
- package/dist/skills/SkillCatalog.d.ts +41 -0
- package/dist/skills/SkillCatalog.js +119 -0
- package/dist/skills/SkillCatalog.js.map +1 -0
- package/dist/skills/SkillDocument.d.ts +21 -0
- package/dist/skills/SkillDocument.js +85 -0
- package/dist/skills/SkillDocument.js.map +1 -0
- package/dist/skills/SkillResolution.d.ts +34 -0
- package/dist/skills/SkillResolution.js +217 -0
- package/dist/skills/SkillResolution.js.map +1 -0
- package/{doc → docs}/documentation/README.md +4 -1
- package/docs/documentation/actions.md +97 -0
- package/docs/documentation/best-practices.md +105 -0
- package/{doc → docs}/documentation/profile-repository.md +74 -6
- package/docs/documentation/skills.md +436 -0
- package/{doc → docs}/documentation/support-matrix.md +4 -1
- package/package.json +4 -8
- package/src/schemas/profile.schema.json +40 -3
- package/dist/agents/OutfitterDocs.d.ts +0 -2
- package/dist/agents/OutfitterDocs.js +0 -38
- package/dist/agents/OutfitterDocs.js.map +0 -1
- package/skills/outfitter/SKILL.md +0 -68
- /package/{doc → docs}/architecture/state_writeback_strategy.md +0 -0
- /package/{doc → docs}/documentation/cli.md +0 -0
- /package/{doc → docs}/documentation/concepts.md +0 -0
- /package/{doc → docs}/documentation/first-time-cli-agent-users.md +0 -0
- /package/{doc → docs}/documentation/getting-started.md +0 -0
- /package/{doc → docs}/documentation/iterating-on-profiles.md +0 -0
- /package/{doc → docs}/documentation/profiles.md +0 -0
- /package/{doc → docs}/documentation/state.md +0 -0
- /package/{doc → docs}/documentation/switching-to-outfitter.md +0 -0
- /package/{doc → docs}/documentation/usecases/engineering.md +0 -0
- /package/{doc → docs}/documentation/usecases/organization-profile-catalog.md +0 -0
- /package/{doc → docs}/documentation/usecases/persona-reviews.md +0 -0
- /package/{doc → docs}/philosophy.md +0 -0
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
// Resolves selected skill IDs, validates references, and materializes generated skills.
|
|
2
|
+
import { copyFileSync, cpSync, existsSync, mkdirSync, realpathSync, statSync } from 'node:fs';
|
|
3
|
+
import { basename, isAbsolute, join, relative, sep } from 'node:path';
|
|
4
|
+
import { isSkillDocumentIssue, isValidSkillId, readSkillDocument, skillMaterializationSections, } from './SkillDocument.js';
|
|
5
|
+
export const resolveSkillEntries = (input) => {
|
|
6
|
+
const sources = [];
|
|
7
|
+
const diagnostics = [];
|
|
8
|
+
for (const entryInput of input.entries) {
|
|
9
|
+
const outcome = resolveSkillEntry(input, entryInput);
|
|
10
|
+
sources.push(...outcome.sources);
|
|
11
|
+
diagnostics.push(...outcome.diagnostics);
|
|
12
|
+
}
|
|
13
|
+
return { sources, diagnostics };
|
|
14
|
+
};
|
|
15
|
+
const resolveSkillEntry = (input, entryInput) => {
|
|
16
|
+
const { entry, profileReferenceRoot } = entryInput;
|
|
17
|
+
const selection = typeof entry === 'string' ? { id: entry } : entry;
|
|
18
|
+
const catalogEntry = input.catalog.entries.get(selection.id);
|
|
19
|
+
if (catalogEntry === undefined) {
|
|
20
|
+
return resolveUncataloguedEntry(entry, selection.id);
|
|
21
|
+
}
|
|
22
|
+
const references = selection.references ?? [];
|
|
23
|
+
const referencesKey = JSON.stringify(references);
|
|
24
|
+
const cached = input.materializationCache?.get(selection.id);
|
|
25
|
+
if (cached !== undefined) {
|
|
26
|
+
return resolveCachedEntry(cached, referencesKey, selection.id);
|
|
27
|
+
}
|
|
28
|
+
const resolved = resolveCatalogSkill({
|
|
29
|
+
catalogEntry,
|
|
30
|
+
references,
|
|
31
|
+
profileReferenceRoot,
|
|
32
|
+
projectDirectory: input.projectDirectory,
|
|
33
|
+
outputDirectory: input.outputDirectory,
|
|
34
|
+
});
|
|
35
|
+
input.materializationCache?.set(selection.id, {
|
|
36
|
+
referencesKey,
|
|
37
|
+
generatedDirectory: resolved.generatedDirectory,
|
|
38
|
+
});
|
|
39
|
+
return { sources: toEntrySources(resolved.generatedDirectory), diagnostics: resolved.diagnostics };
|
|
40
|
+
};
|
|
41
|
+
const resolveCachedEntry = (cached, referencesKey, id) => {
|
|
42
|
+
if (cached.referencesKey !== referencesKey) {
|
|
43
|
+
return {
|
|
44
|
+
sources: [],
|
|
45
|
+
diagnostics: [
|
|
46
|
+
{
|
|
47
|
+
severity: 'error',
|
|
48
|
+
path: `controls.skills/${id}`,
|
|
49
|
+
message: `Skill '${id}' is selected with conflicting reference sets across control scopes.`,
|
|
50
|
+
},
|
|
51
|
+
],
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
return { sources: toEntrySources(cached.generatedDirectory), diagnostics: [] };
|
|
55
|
+
};
|
|
56
|
+
const toEntrySources = (generatedDirectory) => generatedDirectory === undefined ? [] : [generatedDirectory];
|
|
57
|
+
const resolveUncataloguedEntry = (entry, id) => {
|
|
58
|
+
if (typeof entry !== 'string') {
|
|
59
|
+
return {
|
|
60
|
+
sources: [],
|
|
61
|
+
diagnostics: [
|
|
62
|
+
{
|
|
63
|
+
severity: 'error',
|
|
64
|
+
path: `controls.skills/${id}`,
|
|
65
|
+
message: `Skill ID '${id}' does not resolve in any skills directory.`,
|
|
66
|
+
},
|
|
67
|
+
],
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
if (isValidSkillId(entry)) {
|
|
71
|
+
return {
|
|
72
|
+
sources: [entry],
|
|
73
|
+
diagnostics: [
|
|
74
|
+
{
|
|
75
|
+
severity: 'warning',
|
|
76
|
+
path: `controls.skills/${entry}`,
|
|
77
|
+
message: `Skill '${entry}' does not resolve in any skills directory; passing it through as a path source.`,
|
|
78
|
+
},
|
|
79
|
+
],
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
return { sources: [entry], diagnostics: [] };
|
|
83
|
+
};
|
|
84
|
+
const resolveCatalogSkill = (input) => {
|
|
85
|
+
const { catalogEntry } = input;
|
|
86
|
+
const diagnostics = [];
|
|
87
|
+
const document = readSkillDocument(catalogEntry.skillPath);
|
|
88
|
+
if (isSkillDocumentIssue(document)) {
|
|
89
|
+
return { diagnostics: [{ severity: 'error', path: document.path, message: document.message }] };
|
|
90
|
+
}
|
|
91
|
+
if (document.name !== catalogEntry.id) {
|
|
92
|
+
return {
|
|
93
|
+
diagnostics: [
|
|
94
|
+
{
|
|
95
|
+
severity: 'error',
|
|
96
|
+
path: catalogEntry.skillPath,
|
|
97
|
+
message: `SKILL.md 'name' ('${document.name}') must match its directory name ('${catalogEntry.id}').`,
|
|
98
|
+
},
|
|
99
|
+
],
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
const materializations = planSkillMaterializations(input, document, diagnostics);
|
|
103
|
+
if (diagnostics.some((diagnostic) => diagnostic.severity === 'error') || input.outputDirectory === undefined) {
|
|
104
|
+
return { diagnostics };
|
|
105
|
+
}
|
|
106
|
+
const generatedDirectory = join(input.outputDirectory, catalogEntry.id);
|
|
107
|
+
cpSync(catalogEntry.directory, generatedDirectory, { recursive: true });
|
|
108
|
+
for (const materialization of materializations) {
|
|
109
|
+
const sectionDirectory = join(generatedDirectory, materialization.section);
|
|
110
|
+
mkdirSync(sectionDirectory, { recursive: true });
|
|
111
|
+
// copyFileSync preserves the source mode, so materialized scripts stay executable.
|
|
112
|
+
copyFileSync(materialization.sourcePath, join(sectionDirectory, materialization.destinationName));
|
|
113
|
+
}
|
|
114
|
+
return { generatedDirectory, diagnostics };
|
|
115
|
+
};
|
|
116
|
+
const planSkillMaterializations = (input, document, diagnostics) => {
|
|
117
|
+
const materializations = [];
|
|
118
|
+
for (const section of skillMaterializationSections) {
|
|
119
|
+
const entries = [
|
|
120
|
+
...document[section].map((reference) => ({ reference, fileRoot: input.catalogEntry.referenceRoot })),
|
|
121
|
+
// Profile-added references resolve `file` from the declaring profile's repository.
|
|
122
|
+
...(section === 'references'
|
|
123
|
+
? input.references.map((reference) => ({
|
|
124
|
+
reference,
|
|
125
|
+
fileRoot: input.profileReferenceRoot ?? input.projectDirectory ?? input.catalogEntry.referenceRoot,
|
|
126
|
+
}))
|
|
127
|
+
: []),
|
|
128
|
+
];
|
|
129
|
+
materializations.push(...planSectionMaterializations(input, section, entries, diagnostics));
|
|
130
|
+
}
|
|
131
|
+
return materializations;
|
|
132
|
+
};
|
|
133
|
+
const planSectionMaterializations = (input, section, entries, diagnostics) => {
|
|
134
|
+
const materializations = [];
|
|
135
|
+
const destinationNames = new Set();
|
|
136
|
+
for (const { reference, fileRoot } of entries) {
|
|
137
|
+
const resolved = resolveReferenceMaterialization({
|
|
138
|
+
reference,
|
|
139
|
+
section,
|
|
140
|
+
fileRoot,
|
|
141
|
+
projectDirectory: input.projectDirectory,
|
|
142
|
+
skillPath: input.catalogEntry.skillPath,
|
|
143
|
+
});
|
|
144
|
+
if (resolved === undefined) {
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
147
|
+
if ('message' in resolved) {
|
|
148
|
+
diagnostics.push(resolved);
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
if (destinationNames.has(resolved.destinationName)) {
|
|
152
|
+
diagnostics.push({
|
|
153
|
+
severity: 'error',
|
|
154
|
+
path: input.catalogEntry.skillPath,
|
|
155
|
+
message: `Destination '${section}/${resolved.destinationName}' is declared more than once.`,
|
|
156
|
+
});
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
// Checked against the source skill directory during planning so lint reports
|
|
160
|
+
// shipped-file collisions without materializing anything.
|
|
161
|
+
if (existsSync(join(input.catalogEntry.directory, section, resolved.destinationName))) {
|
|
162
|
+
diagnostics.push({
|
|
163
|
+
severity: 'error',
|
|
164
|
+
path: input.catalogEntry.skillPath,
|
|
165
|
+
message: `Destination '${section}/${resolved.destinationName}' collides with a file ` +
|
|
166
|
+
`already shipped by skill '${input.catalogEntry.id}'.`,
|
|
167
|
+
});
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
170
|
+
destinationNames.add(resolved.destinationName);
|
|
171
|
+
materializations.push(resolved);
|
|
172
|
+
}
|
|
173
|
+
return materializations;
|
|
174
|
+
};
|
|
175
|
+
const describeReference = (reference, fileRoot, projectDirectory) => 'repo_file' in reference
|
|
176
|
+
? { declaredPath: reference.repo_file, root: projectDirectory, kind: 'repo_file', rootLabel: 'project' }
|
|
177
|
+
: { declaredPath: reference.file, root: fileRoot, kind: 'file', rootLabel: 'catalog' };
|
|
178
|
+
const resolveReferenceMaterialization = (input) => {
|
|
179
|
+
const { declaredPath, root, kind, rootLabel } = describeReference(input.reference, input.fileRoot, input.projectDirectory);
|
|
180
|
+
const error = (message) => ({
|
|
181
|
+
severity: 'error',
|
|
182
|
+
path: input.skillPath,
|
|
183
|
+
message,
|
|
184
|
+
});
|
|
185
|
+
if (root === undefined) {
|
|
186
|
+
return error(`Reference ${kind} '${declaredPath}' has no ${rootLabel} root to resolve from.`);
|
|
187
|
+
}
|
|
188
|
+
const sourcePath = isAbsolute(declaredPath) ? declaredPath : join(root, declaredPath);
|
|
189
|
+
if (!existsSync(sourcePath)) {
|
|
190
|
+
// A consuming project may not contain a repo_file target; the reference is omitted.
|
|
191
|
+
return kind === 'repo_file' ? undefined : error(`Reference file '${declaredPath}' was not found under '${root}'.`);
|
|
192
|
+
}
|
|
193
|
+
if (!statSync(sourcePath).isFile()) {
|
|
194
|
+
return error(`Reference ${kind} '${declaredPath}' must be a regular file.`);
|
|
195
|
+
}
|
|
196
|
+
if (escapesRoot(sourcePath, root)) {
|
|
197
|
+
return error(`Reference ${kind} '${declaredPath}' resolves outside its ${rootLabel} root '${root}'.`);
|
|
198
|
+
}
|
|
199
|
+
return { section: input.section, sourcePath, destinationName: basename(declaredPath) };
|
|
200
|
+
};
|
|
201
|
+
/** Targets must remain within their root after following symlinks. */
|
|
202
|
+
const escapesRoot = (sourcePath, root) => {
|
|
203
|
+
let resolvedSource;
|
|
204
|
+
let resolvedRoot;
|
|
205
|
+
try {
|
|
206
|
+
resolvedSource = realpathSync(sourcePath);
|
|
207
|
+
resolvedRoot = realpathSync(root);
|
|
208
|
+
}
|
|
209
|
+
catch {
|
|
210
|
+
/* v8 ignore next 2 -- existsSync guards the source; a vanished root is a race we treat as escaping. */
|
|
211
|
+
return true;
|
|
212
|
+
}
|
|
213
|
+
const relativePath = relative(resolvedRoot, resolvedSource);
|
|
214
|
+
// A bare '..' or a '../' prefix escapes; a sibling name like '..config' does not.
|
|
215
|
+
return relativePath === '..' || relativePath.startsWith(`..${sep}`) || isAbsolute(relativePath);
|
|
216
|
+
};
|
|
217
|
+
//# sourceMappingURL=SkillResolution.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SkillResolution.js","sourceRoot":"","sources":["../../src/skills/SkillResolution.ts"],"names":[],"mappings":"AAAA,wFAAwF;AACxF,OAAO,EAAE,YAAY,EAAE,MAAM,EAAE,UAAU,EAAE,SAAS,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAC9F,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAE,IAAI,EAAE,QAAQ,EAAE,GAAG,EAAE,MAAM,WAAW,CAAC;AAItE,OAAO,EACL,oBAAoB,EACpB,cAAc,EACd,iBAAiB,EACjB,4BAA4B,GAG7B,MAAM,oBAAoB,CAAC;AAsC5B,MAAM,CAAC,MAAM,mBAAmB,GAAG,CAAC,KAA2B,EAAyB,EAAE;IACxF,MAAM,OAAO,GAAa,EAAE,CAAC;IAC7B,MAAM,WAAW,GAAgC,EAAE,CAAC;IAEpD,KAAK,MAAM,UAAU,IAAI,KAAK,CAAC,OAAO,EAAE,CAAC;QACvC,MAAM,OAAO,GAAG,iBAAiB,CAAC,KAAK,EAAE,UAAU,CAAC,CAAC;QACrD,OAAO,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;QACjC,WAAW,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC;IAC3C,CAAC;IAED,OAAO,EAAE,OAAO,EAAE,WAAW,EAAE,CAAC;AAClC,CAAC,CAAC;AAEF,MAAM,iBAAiB,GAAG,CAAC,KAA2B,EAAE,UAA2B,EAAyB,EAAE;IAC5G,MAAM,EAAE,KAAK,EAAE,oBAAoB,EAAE,GAAG,UAAU,CAAC;IACnD,MAAM,SAAS,GAAmB,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC;IACpF,MAAM,YAAY,GAAG,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC;IAE7D,IAAI,YAAY,KAAK,SAAS,EAAE,CAAC;QAC/B,OAAO,wBAAwB,CAAC,KAAK,EAAE,SAAS,CAAC,EAAE,CAAC,CAAC;IACvD,CAAC;IAED,MAAM,UAAU,GAAG,SAAS,CAAC,UAAU,IAAI,EAAE,CAAC;IAC9C,MAAM,aAAa,GAAG,IAAI,CAAC,SAAS,CAAC,UAAU,CAAC,CAAC;IACjD,MAAM,MAAM,GAAG,KAAK,CAAC,oBAAoB,EAAE,GAAG,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC;IAE7D,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;QACzB,OAAO,kBAAkB,CAAC,MAAM,EAAE,aAAa,EAAE,SAAS,CAAC,EAAE,CAAC,CAAC;IACjE,CAAC;IAED,MAAM,QAAQ,GAAG,mBAAmB,CAAC;QACnC,YAAY;QACZ,UAAU;QACV,oBAAoB;QACpB,gBAAgB,EAAE,KAAK,CAAC,gBAAgB;QACxC,eAAe,EAAE,KAAK,CAAC,eAAe;KACvC,CAAC,CAAC;IACH,KAAK,CAAC,oBAAoB,EAAE,GAAG,CAAC,SAAS,CAAC,EAAE,EAAE;QAC5C,aAAa;QACb,kBAAkB,EAAE,QAAQ,CAAC,kBAAkB;KAChD,CAAC,CAAC;IAEH,OAAO,EAAE,OAAO,EAAE,cAAc,CAAC,QAAQ,CAAC,kBAAkB,CAAC,EAAE,WAAW,EAAE,QAAQ,CAAC,WAAW,EAAE,CAAC;AACrG,CAAC,CAAC;AAEF,MAAM,kBAAkB,GAAG,CACzB,MAAgF,EAChF,aAAqB,EACrB,EAAU,EACa,EAAE;IACzB,IAAI,MAAM,CAAC,aAAa,KAAK,aAAa,EAAE,CAAC;QAC3C,OAAO;YACL,OAAO,EAAE,EAAE;YACX,WAAW,EAAE;gBACX;oBACE,QAAQ,EAAE,OAAO;oBACjB,IAAI,EAAE,mBAAmB,EAAE,EAAE;oBAC7B,OAAO,EAAE,UAAU,EAAE,sEAAsE;iBAC5F;aACF;SACF,CAAC;IACJ,CAAC;IAED,OAAO,EAAE,OAAO,EAAE,cAAc,CAAC,MAAM,CAAC,kBAAkB,CAAC,EAAE,WAAW,EAAE,EAAE,EAAE,CAAC;AACjF,CAAC,CAAC;AAEF,MAAM,cAAc,GAAG,CAAC,kBAAsC,EAAqB,EAAE,CACnF,kBAAkB,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,kBAAkB,CAAC,CAAC;AAE/D,MAAM,wBAAwB,GAAG,CAAC,KAAwB,EAAE,EAAU,EAAyB,EAAE;IAC/F,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QAC9B,OAAO;YACL,OAAO,EAAE,EAAE;YACX,WAAW,EAAE;gBACX;oBACE,QAAQ,EAAE,OAAO;oBACjB,IAAI,EAAE,mBAAmB,EAAE,EAAE;oBAC7B,OAAO,EAAE,aAAa,EAAE,6CAA6C;iBACtE;aACF;SACF,CAAC;IACJ,CAAC;IAED,IAAI,cAAc,CAAC,KAAK,CAAC,EAAE,CAAC;QAC1B,OAAO;YACL,OAAO,EAAE,CAAC,KAAK,CAAC;YAChB,WAAW,EAAE;gBACX;oBACE,QAAQ,EAAE,SAAS;oBACnB,IAAI,EAAE,mBAAmB,KAAK,EAAE;oBAChC,OAAO,EAAE,UAAU,KAAK,kFAAkF;iBAC3G;aACF;SACF,CAAC;IACJ,CAAC;IAED,OAAO,EAAE,OAAO,EAAE,CAAC,KAAK,CAAC,EAAE,WAAW,EAAE,EAAE,EAAE,CAAC;AAC/C,CAAC,CAAC;AAWF,MAAM,mBAAmB,GAAG,CAC1B,KAAkC,EACoE,EAAE;IACxG,MAAM,EAAE,YAAY,EAAE,GAAG,KAAK,CAAC;IAC/B,MAAM,WAAW,GAAgC,EAAE,CAAC;IACpD,MAAM,QAAQ,GAAG,iBAAiB,CAAC,YAAY,CAAC,SAAS,CAAC,CAAC;IAE3D,IAAI,oBAAoB,CAAC,QAAQ,CAAC,EAAE,CAAC;QACnC,OAAO,EAAE,WAAW,EAAE,CAAC,EAAE,QAAQ,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,CAAC,IAAI,EAAE,OAAO,EAAE,QAAQ,CAAC,OAAO,EAAE,CAAC,EAAE,CAAC;IAClG,CAAC;IAED,IAAI,QAAQ,CAAC,IAAI,KAAK,YAAY,CAAC,EAAE,EAAE,CAAC;QACtC,OAAO;YACL,WAAW,EAAE;gBACX;oBACE,QAAQ,EAAE,OAAO;oBACjB,IAAI,EAAE,YAAY,CAAC,SAAS;oBAC5B,OAAO,EAAE,qBAAqB,QAAQ,CAAC,IAAI,sCAAsC,YAAY,CAAC,EAAE,KAAK;iBACtG;aACF;SACF,CAAC;IACJ,CAAC;IAED,MAAM,gBAAgB,GAAG,yBAAyB,CAAC,KAAK,EAAE,QAAQ,EAAE,WAAW,CAAC,CAAC;IAEjF,IAAI,WAAW,CAAC,IAAI,CAAC,CAAC,UAAU,EAAE,EAAE,CAAC,UAAU,CAAC,QAAQ,KAAK,OAAO,CAAC,IAAI,KAAK,CAAC,eAAe,KAAK,SAAS,EAAE,CAAC;QAC7G,OAAO,EAAE,WAAW,EAAE,CAAC;IACzB,CAAC;IAED,MAAM,kBAAkB,GAAG,IAAI,CAAC,KAAK,CAAC,eAAe,EAAE,YAAY,CAAC,EAAE,CAAC,CAAC;IACxE,MAAM,CAAC,YAAY,CAAC,SAAS,EAAE,kBAAkB,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAExE,KAAK,MAAM,eAAe,IAAI,gBAAgB,EAAE,CAAC;QAC/C,MAAM,gBAAgB,GAAG,IAAI,CAAC,kBAAkB,EAAE,eAAe,CAAC,OAAO,CAAC,CAAC;QAC3E,SAAS,CAAC,gBAAgB,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QACjD,mFAAmF;QACnF,YAAY,CAAC,eAAe,CAAC,UAAU,EAAE,IAAI,CAAC,gBAAgB,EAAE,eAAe,CAAC,eAAe,CAAC,CAAC,CAAC;IACpG,CAAC;IAED,OAAO,EAAE,kBAAkB,EAAE,WAAW,EAAE,CAAC;AAC7C,CAAC,CAAC;AAQF,MAAM,yBAAyB,GAAG,CAChC,KAAkC,EAClC,QAAuB,EACvB,WAAwC,EACP,EAAE;IACnC,MAAM,gBAAgB,GAA2B,EAAE,CAAC;IAEpD,KAAK,MAAM,OAAO,IAAI,4BAA4B,EAAE,CAAC;QACnD,MAAM,OAAO,GAAG;YACd,GAAG,QAAQ,CAAC,OAAO,CAAC,CAAC,GAAG,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC,CAAC,EAAE,SAAS,EAAE,QAAQ,EAAE,KAAK,CAAC,YAAY,CAAC,aAAa,EAAE,CAAC,CAAC;YACpG,mFAAmF;YACnF,GAAG,CAAC,OAAO,KAAK,YAAY;gBAC1B,CAAC,CAAC,KAAK,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC,CAAC;oBACnC,SAAS;oBACT,QAAQ,EAAE,KAAK,CAAC,oBAAoB,IAAI,KAAK,CAAC,gBAAgB,IAAI,KAAK,CAAC,YAAY,CAAC,aAAa;iBACnG,CAAC,CAAC;gBACL,CAAC,CAAC,EAAE,CAAC;SACR,CAAC;QACF,gBAAgB,CAAC,IAAI,CAAC,GAAG,2BAA2B,CAAC,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,WAAW,CAAC,CAAC,CAAC;IAC9F,CAAC;IAED,OAAO,gBAAgB,CAAC;AAC1B,CAAC,CAAC;AAEF,MAAM,2BAA2B,GAAG,CAClC,KAAkC,EAClC,OAAoC,EACpC,OAAqF,EACrF,WAAwC,EACP,EAAE;IACnC,MAAM,gBAAgB,GAA2B,EAAE,CAAC;IACpD,MAAM,gBAAgB,GAAG,IAAI,GAAG,EAAU,CAAC;IAE3C,KAAK,MAAM,EAAE,SAAS,EAAE,QAAQ,EAAE,IAAI,OAAO,EAAE,CAAC;QAC9C,MAAM,QAAQ,GAAG,+BAA+B,CAAC;YAC/C,SAAS;YACT,OAAO;YACP,QAAQ;YACR,gBAAgB,EAAE,KAAK,CAAC,gBAAgB;YACxC,SAAS,EAAE,KAAK,CAAC,YAAY,CAAC,SAAS;SACxC,CAAC,CAAC;QAEH,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;YAC3B,SAAS;QACX,CAAC;QAED,IAAI,SAAS,IAAI,QAAQ,EAAE,CAAC;YAC1B,WAAW,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;YAC3B,SAAS;QACX,CAAC;QAED,IAAI,gBAAgB,CAAC,GAAG,CAAC,QAAQ,CAAC,eAAe,CAAC,EAAE,CAAC;YACnD,WAAW,CAAC,IAAI,CAAC;gBACf,QAAQ,EAAE,OAAO;gBACjB,IAAI,EAAE,KAAK,CAAC,YAAY,CAAC,SAAS;gBAClC,OAAO,EAAE,gBAAgB,OAAO,IAAI,QAAQ,CAAC,eAAe,+BAA+B;aAC5F,CAAC,CAAC;YACH,SAAS;QACX,CAAC;QAED,6EAA6E;QAC7E,0DAA0D;QAC1D,IAAI,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,SAAS,EAAE,OAAO,EAAE,QAAQ,CAAC,eAAe,CAAC,CAAC,EAAE,CAAC;YACtF,WAAW,CAAC,IAAI,CAAC;gBACf,QAAQ,EAAE,OAAO;gBACjB,IAAI,EAAE,KAAK,CAAC,YAAY,CAAC,SAAS;gBAClC,OAAO,EACL,gBAAgB,OAAO,IAAI,QAAQ,CAAC,eAAe,yBAAyB;oBAC5E,6BAA6B,KAAK,CAAC,YAAY,CAAC,EAAE,IAAI;aACzD,CAAC,CAAC;YACH,SAAS;QACX,CAAC;QAED,gBAAgB,CAAC,GAAG,CAAC,QAAQ,CAAC,eAAe,CAAC,CAAC;QAC/C,gBAAgB,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IAClC,CAAC;IAED,OAAO,gBAAgB,CAAC;AAC1B,CAAC,CAAC;AASF,MAAM,iBAAiB,GAAG,CACxB,SAAyB,EACzB,QAAgB,EAChB,gBAAoC,EAChB,EAAE,CACtB,WAAW,IAAI,SAAS;IACtB,CAAC,CAAC,EAAE,YAAY,EAAE,SAAS,CAAC,SAAS,EAAE,IAAI,EAAE,gBAAgB,EAAE,IAAI,EAAE,WAAW,EAAE,SAAS,EAAE,SAAS,EAAE;IACxG,CAAC,CAAC,EAAE,YAAY,EAAE,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,SAAS,EAAE,CAAC;AAE3F,MAAM,+BAA+B,GAAG,CAAC,KAMxC,EAAgE,EAAE;IACjE,MAAM,EAAE,YAAY,EAAE,IAAI,EAAE,IAAI,EAAE,SAAS,EAAE,GAAG,iBAAiB,CAC/D,KAAK,CAAC,SAAS,EACf,KAAK,CAAC,QAAQ,EACd,KAAK,CAAC,gBAAgB,CACvB,CAAC;IACF,MAAM,KAAK,GAAG,CAAC,OAAe,EAA6B,EAAE,CAAC,CAAC;QAC7D,QAAQ,EAAE,OAAO;QACjB,IAAI,EAAE,KAAK,CAAC,SAAS;QACrB,OAAO;KACR,CAAC,CAAC;IAEH,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;QACvB,OAAO,KAAK,CAAC,aAAa,IAAI,KAAK,YAAY,YAAY,SAAS,wBAAwB,CAAC,CAAC;IAChG,CAAC;IAED,MAAM,UAAU,GAAG,UAAU,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,EAAE,YAAY,CAAC,CAAC;IAEtF,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;QAC5B,oFAAoF;QACpF,OAAO,IAAI,KAAK,WAAW,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC,mBAAmB,YAAY,0BAA0B,IAAI,IAAI,CAAC,CAAC;IACrH,CAAC;IAED,IAAI,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC;QACnC,OAAO,KAAK,CAAC,aAAa,IAAI,KAAK,YAAY,2BAA2B,CAAC,CAAC;IAC9E,CAAC;IAED,IAAI,WAAW,CAAC,UAAU,EAAE,IAAI,CAAC,EAAE,CAAC;QAClC,OAAO,KAAK,CAAC,aAAa,IAAI,KAAK,YAAY,0BAA0B,SAAS,UAAU,IAAI,IAAI,CAAC,CAAC;IACxG,CAAC;IAED,OAAO,EAAE,OAAO,EAAE,KAAK,CAAC,OAAO,EAAE,UAAU,EAAE,eAAe,EAAE,QAAQ,CAAC,YAAY,CAAC,EAAE,CAAC;AACzF,CAAC,CAAC;AAEF,sEAAsE;AACtE,MAAM,WAAW,GAAG,CAAC,UAAkB,EAAE,IAAY,EAAW,EAAE;IAChE,IAAI,cAAsB,CAAC;IAC3B,IAAI,YAAoB,CAAC;IAEzB,IAAI,CAAC;QACH,cAAc,GAAG,YAAY,CAAC,UAAU,CAAC,CAAC;QAC1C,YAAY,GAAG,YAAY,CAAC,IAAI,CAAC,CAAC;IACpC,CAAC;IAAC,MAAM,CAAC;QACP,uGAAuG;QACvG,OAAO,IAAI,CAAC;IACd,CAAC;IAED,MAAM,YAAY,GAAG,QAAQ,CAAC,YAAY,EAAE,cAAc,CAAC,CAAC;IAE5D,kFAAkF;IAClF,OAAO,YAAY,KAAK,IAAI,IAAI,YAAY,CAAC,UAAU,CAAC,KAAK,GAAG,EAAE,CAAC,IAAI,UAAU,CAAC,YAAY,CAAC,CAAC;AAClG,CAAC,CAAC"}
|
|
@@ -8,8 +8,11 @@ User-facing Outfitter documentation.
|
|
|
8
8
|
- [First-time CLI agent users](./first-time-cli-agent-users.md)
|
|
9
9
|
- [Switching to Outfitter](./switching-to-outfitter.md)
|
|
10
10
|
- [Profiles](./profiles.md)
|
|
11
|
-
- [
|
|
11
|
+
- [Skills](./skills.md) — Define project and profile-bundled skills, route to specialized resources, and compose external references.
|
|
12
|
+
- [Best practices](./best-practices.md) — Prefer a few stable profiles and many focused, progressively disclosed skills.
|
|
13
|
+
- [Profile repositories](./profile-repository.md) — Publish and consume shareable profiles and standalone skills.
|
|
12
14
|
- [Iterating on local and worktree profiles](./iterating-on-profiles.md)
|
|
15
|
+
- [Running profiles in GitHub Actions](./actions.md)
|
|
13
16
|
- [Adapter support matrix](./support-matrix.md)
|
|
14
17
|
- [State persistence](./state.md)
|
|
15
18
|
- [Philosophy](../philosophy.md)
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# Running profiles in GitHub Actions
|
|
2
|
+
|
|
3
|
+
[`ai-outfitter/actions`](https://github.com/ai-outfitter/actions) runs an Outfitter profile non-interactively inside a GitHub Actions workflow. Outfitter assembles the profile exactly as it does locally — context, prompts, skills, controls — and launches the agent CLI in headless print mode (`pi -p`), so the agent does one unit of work per workflow run and exits. Wire it to any trigger and a profile becomes a CI agent: a PR reviewer, a scheduled commit auditor, an issue triager.
|
|
4
|
+
|
|
5
|
+
```yaml
|
|
6
|
+
# .github/workflows/issue-triage.yml
|
|
7
|
+
name: Issue triage
|
|
8
|
+
on:
|
|
9
|
+
issues:
|
|
10
|
+
types: [opened]
|
|
11
|
+
|
|
12
|
+
permissions:
|
|
13
|
+
contents: read
|
|
14
|
+
issues: write
|
|
15
|
+
models: read
|
|
16
|
+
|
|
17
|
+
jobs:
|
|
18
|
+
triage:
|
|
19
|
+
if: github.event.issue.user.type != 'Bot'
|
|
20
|
+
runs-on: ubuntu-latest
|
|
21
|
+
steps:
|
|
22
|
+
- uses: actions/checkout@v4
|
|
23
|
+
- uses: ai-outfitter/actions@v1
|
|
24
|
+
with:
|
|
25
|
+
profile: issue-triage
|
|
26
|
+
profile-source: ${{ github.workspace }}/profiles
|
|
27
|
+
prompt: >-
|
|
28
|
+
Triage issue #${{ github.event.issue.number }} in
|
|
29
|
+
${{ github.repository }} following your process.
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
The action installs `@ai-outfitter/outfitter`, writes a minimal `~/.outfitter/settings.yml` on the runner, syncs remote catalogs if needed, then runs `outfitter run --profile <profile> --agent pi -- -p "<prompt>"`. The runner is discarded afterwards; nothing persists except what the agent pushed through its token.
|
|
33
|
+
|
|
34
|
+
Profiles can come from the checked-out repository itself (a path, as above), an `owner/repo` catalog shorthand, or any git URI — the same [profile repository](./profile-repository.md) sources Outfitter supports locally. Pin `profile-source-ref` for catalogs you don't own.
|
|
35
|
+
|
|
36
|
+
## Pass GitHub trigger context
|
|
37
|
+
|
|
38
|
+
`trigger_context` is a convention for the initial prompt passed by an
|
|
39
|
+
`ai-outfitter/actions` workflow. It is not an Outfitter object and Outfitter does
|
|
40
|
+
not create or parse it. GitHub Actions interpolates the selected `github`
|
|
41
|
+
expression values before invoking Outfitter, giving the profile metadata that
|
|
42
|
+
the workflow — not the event author — chose to pass. That does not make every
|
|
43
|
+
value trustworthy: branch and tag names, labels, titles, and logins are
|
|
44
|
+
user-influenced, so route on them as opaque identifiers and never treat them
|
|
45
|
+
as instructions.
|
|
46
|
+
|
|
47
|
+
```yaml
|
|
48
|
+
- uses: ai-outfitter/actions@v1
|
|
49
|
+
with:
|
|
50
|
+
profile: platform
|
|
51
|
+
prompt: |
|
|
52
|
+
Handle this GitHub event according to the profile's skill activation rules.
|
|
53
|
+
|
|
54
|
+
trigger_context:
|
|
55
|
+
repository: ${{ github.repository }}
|
|
56
|
+
event_name: ${{ github.event_name }}
|
|
57
|
+
issue_number: ${{ github.event.issue.number || '' }}
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Include only the identifiers the profile's routing rules need for the
|
|
61
|
+
workflow's declared events (for example `sha`, `issue_labels`, or a
|
|
62
|
+
deployment's `environment_url` when those events are in play). Add a
|
|
63
|
+
workflow-owned discriminator, such as `report_kind: weekly-kpi`, when GitHub's
|
|
64
|
+
event metadata cannot distinguish scheduled behaviors.
|
|
65
|
+
|
|
66
|
+
Keep the profile's mapping from this metadata to skills short and keep task
|
|
67
|
+
procedures in the skills themselves. See
|
|
68
|
+
[Keep routing concise](./best-practices.md#keep-routing-concise) for that design
|
|
69
|
+
boundary.
|
|
70
|
+
|
|
71
|
+
Do not interpolate issue bodies, pull request bodies, comments, diffs,
|
|
72
|
+
deployment logs, or fetched page content into `trigger_context`. Pass stable
|
|
73
|
+
identifiers, select the relevant skill, and let that skill retrieve only the
|
|
74
|
+
untrusted source material it needs with trusted tools.
|
|
75
|
+
|
|
76
|
+
## Zero-key inference with GitHub Models
|
|
77
|
+
|
|
78
|
+
CI agents don't need a paid provider key. [GitHub Models](https://docs.github.com/en/github-models) serves hosted models authenticated by the workflow's own `GITHUB_TOKEN`: grant `models: read` in the `permissions:` block, commit a pi provider config pointing at `https://models.github.ai/inference` with `"apiKey": "$GITHUB_TOKEN"`, install it to `~/.pi/agent/models.json` before the action step, and select the provider in the profile's `controls`. The action's README documents the full recipe, including model-selection gotchas (catalog availability, tool-call wire compatibility, and models too weak to hold an agentic loop).
|
|
79
|
+
|
|
80
|
+
Mind the rate limits: the included tier is sized for event-driven, one-shot jobs. Concurrent runs of a large model can 429; high-volume review loops need a provider key.
|
|
81
|
+
|
|
82
|
+
## Write profiles for headless runs
|
|
83
|
+
|
|
84
|
+
A profile that behaves well interactively can still fail silently in CI. Lessons from running triage agents in production:
|
|
85
|
+
|
|
86
|
+
- **Stdout is invisible.** In print mode nobody reads what the agent says — only its side effects matter. Instruct the profile to _do_ things with `gh` (comment, label, push) and name the exact commands; otherwise models will print the deliverable as their answer and exit green.
|
|
87
|
+
- **Quote-safe posting.** When the agent posts text derived from untrusted input (issue bodies, diffs) back through `gh`, require a quoted heredoc plus `--body-file`, never inline `--body "..."` — backticks in a double-quoted body are executed by the shell.
|
|
88
|
+
- **Verify side effects, not exit codes.** A green run is not proof of work. Add a post-agent step that asserts the expected side effects landed — the action ships [`scripts/validate-triage.sh`](https://github.com/ai-outfitter/actions/blob/main/scripts/validate-triage.sh) as a reference for triage-style jobs.
|
|
89
|
+
- **Hard limits in the profile.** Enumerate exactly what the agent may do (which labels, how many comments, no closing/editing) and treat fetched content as data to classify, never instructions.
|
|
90
|
+
|
|
91
|
+
## Scope the token
|
|
92
|
+
|
|
93
|
+
The agent runs arbitrary `gh`/`git`/shell with whatever token you hand it, against untrusted input. Prefer the workflow's own `GITHUB_TOKEN` with an explicit least-privilege `permissions:` block; use a fine-grained PAT from a dedicated machine account only when the agent needs its own identity. Never use a human's PAT. The action's [token-permissions](https://github.com/ai-outfitter/actions/blob/main/docs/token-permissions.md) and [bot-account](https://github.com/ai-outfitter/actions/blob/main/docs/bot-account.md) guides cover this in depth, including prompt-injection trust boundaries.
|
|
94
|
+
|
|
95
|
+
## More examples
|
|
96
|
+
|
|
97
|
+
The action's [`examples/`](https://github.com/ai-outfitter/actions/tree/main/examples) directory covers scheduled commit review, PR ready-for-review reviews, sensitive-path audits, assigned-task agents, and zero-key issue triage on GitHub Models. A complete live setup — workflow, profile, provider config, and validation — runs in [`ai-outfitter/default-profiles`](https://github.com/ai-outfitter/default-profiles) as its own issue-triage agent.
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
# Best practices
|
|
2
|
+
|
|
3
|
+
Outfitter works best with a few stable profiles and many focused skills.
|
|
4
|
+
Profiles define who the agent is and the boundaries it operates within. Skills
|
|
5
|
+
define what the agent can progressively learn to do.
|
|
6
|
+
|
|
7
|
+
## Prefer a few profiles and many skills
|
|
8
|
+
|
|
9
|
+
Create a profile for a durable identity or policy boundary — engineering,
|
|
10
|
+
platform operations, support, a customer persona. A profile owns model and
|
|
11
|
+
provider controls, operating policy and safety boundaries, tools and
|
|
12
|
+
permissions, conventions, and the short rules for selecting skills.
|
|
13
|
+
|
|
14
|
+
Add a skill when the new behavior is a capability within an existing identity:
|
|
15
|
+
issue planning, code review, deployment smoke testing, KPI reporting, release
|
|
16
|
+
preparation. Adding a new situation SHOULD usually add a skill and a concise
|
|
17
|
+
activation rule, not another profile. Reserve profile inheritance for genuine
|
|
18
|
+
control and policy composition; do not require consumers to inherit a profile
|
|
19
|
+
merely to access one of its skills.
|
|
20
|
+
|
|
21
|
+
Prefer one profile with many skills:
|
|
22
|
+
|
|
23
|
+
```text
|
|
24
|
+
platform profile
|
|
25
|
+
├── issue-planning skill
|
|
26
|
+
├── issue-implementation skill
|
|
27
|
+
├── kpi-reporting skill
|
|
28
|
+
├── deployment-review skill
|
|
29
|
+
└── failed-deployment-triage skill
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Avoid separate `issue-planner`, `deployment-reviewer`, and `kpi-reporter`
|
|
33
|
+
profiles when they share the same platform identity, permissions, and tools.
|
|
34
|
+
Adding release notes later should add a `release-notes` skill to the platform
|
|
35
|
+
profile, not a `release-notes-agent` profile with copies of the same controls.
|
|
36
|
+
|
|
37
|
+
Separate profiles are appropriate when the policy boundary differs. An
|
|
38
|
+
engineering agent may edit code, run tests, and push branches; a customer
|
|
39
|
+
support agent may read customer conversations and draft replies but must not
|
|
40
|
+
modify repositories. Those are different identities with different data access,
|
|
41
|
+
tools, and write permissions, so separate `engineering` and `support` profiles
|
|
42
|
+
are appropriate — and each can still expose many focused skills.
|
|
43
|
+
|
|
44
|
+
## Keep skills focused
|
|
45
|
+
|
|
46
|
+
Give each skill one recognizable capability and a description precise enough
|
|
47
|
+
for an agent to decide when it applies. Keep common policy in the profile
|
|
48
|
+
rather than repeating it across every skill.
|
|
49
|
+
|
|
50
|
+
Focused does not mean tiny. Err on the side of one larger skill that
|
|
51
|
+
[routes to different references](./skills.md#skills-as-routers) over many
|
|
52
|
+
near-duplicate skills: split a skill only when its description can no longer
|
|
53
|
+
say when it applies. Point references at existing human-maintained
|
|
54
|
+
documentation rather than writing new agent-only copies, and use
|
|
55
|
+
[profile-added references](./skills.md#profile-added-references) to specialize
|
|
56
|
+
a shared skill instead of forking it.
|
|
57
|
+
|
|
58
|
+
## Use references for human documentation
|
|
59
|
+
|
|
60
|
+
Keep canonical architecture, policy, and operating documents in normal `docs/`
|
|
61
|
+
locations where people already maintain and review them. Declare those files as
|
|
62
|
+
skill `references` instead of copying them into skill directories, and read
|
|
63
|
+
them only after the skill activates so every run does not pay the context cost
|
|
64
|
+
of every possible workflow. See
|
|
65
|
+
[External references](./skills.md#external-references) for the reference format
|
|
66
|
+
and trust rules.
|
|
67
|
+
|
|
68
|
+
## Keep routing concise
|
|
69
|
+
|
|
70
|
+
A profile's system prompt can map stable runtime signals to relevant skills.
|
|
71
|
+
Keep these activation rules short; detailed procedures belong in the selected
|
|
72
|
+
skills and their references, never repeated in the profile prompt (see
|
|
73
|
+
[Where context and instructions live](./skills.md#where-context-and-instructions-live)).
|
|
74
|
+
|
|
75
|
+
```text
|
|
76
|
+
Select only the skill relevant to the current task.
|
|
77
|
+
- Planning request: use issue-planning.
|
|
78
|
+
- Approved implementation request: use issue-implementation.
|
|
79
|
+
- Recurring repository activity report: use kpi-reporting.
|
|
80
|
+
- Successful environment awaiting verification: use deployment-review.
|
|
81
|
+
- Failed environment update: use failed-deployment-triage.
|
|
82
|
+
Load detailed task content only after selecting the skill.
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
When an integration launches the profile, pass only the trusted identifiers and
|
|
86
|
+
runtime metadata needed to choose a skill. Keep untrusted source material out
|
|
87
|
+
of the activation rules and let the selected skill retrieve only what it needs
|
|
88
|
+
with trusted tools.
|
|
89
|
+
|
|
90
|
+
## Keep automation reusable
|
|
91
|
+
|
|
92
|
+
For agentic automation, prefer a small number of reusable workflows that pass
|
|
93
|
+
concise runtime metadata to the same stable execution profile. Let that profile
|
|
94
|
+
activate the appropriate skill. One profile and one workflow can then support
|
|
95
|
+
issue planning, implementation, scheduled reporting, deployment review, and
|
|
96
|
+
future situations; adding a capability becomes a skill change instead of
|
|
97
|
+
another near-duplicate profile and automation job.
|
|
98
|
+
|
|
99
|
+
## Review the trust chain
|
|
100
|
+
|
|
101
|
+
Profiles and skills can influence agent behavior and tool use. Follow
|
|
102
|
+
[Trust and review](./profile-repository.md#trust-and-review) for catalog
|
|
103
|
+
sources, keep secrets out of profiles, skills, references, and prompts, and run
|
|
104
|
+
`outfitter profile lint --strict` in CI to catch broken skill IDs and
|
|
105
|
+
references before they reach an agent run.
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
# Profile repositories
|
|
2
2
|
|
|
3
|
-
A profile repository (also called a profile catalog) is a git repository that
|
|
3
|
+
A profile repository (also called a profile catalog) is a git repository that
|
|
4
|
+
publishes Outfitter profiles and skills so a team or organization can share
|
|
5
|
+
them. You can bootstrap a machine or project from one, or add one as an ongoing
|
|
6
|
+
source that Outfitter keeps synchronized.
|
|
4
7
|
|
|
5
8
|
```bash
|
|
6
9
|
outfitter setup https://github.com/my_account/outfitter_config
|
|
@@ -15,6 +18,10 @@ outfitter_config/
|
|
|
15
18
|
settings.yml
|
|
16
19
|
profiles/
|
|
17
20
|
engineering-default/profile.yml
|
|
21
|
+
skills/
|
|
22
|
+
outfitter-actions/SKILL.md
|
|
23
|
+
docs/
|
|
24
|
+
actions-design.md
|
|
18
25
|
```
|
|
19
26
|
|
|
20
27
|
or a `.outfitter/` folder:
|
|
@@ -25,7 +32,11 @@ outfitter_config/
|
|
|
25
32
|
settings.yml
|
|
26
33
|
profiles/
|
|
27
34
|
engineering-default/profile.yml
|
|
35
|
+
skills/
|
|
36
|
+
outfitter-actions/SKILL.md
|
|
28
37
|
deepwork/jobs/
|
|
38
|
+
docs/
|
|
39
|
+
actions-design.md
|
|
29
40
|
```
|
|
30
41
|
|
|
31
42
|
Inside the profiles directory, both profile layouts work:
|
|
@@ -35,6 +46,56 @@ Inside the profiles directory, both profile layouts work:
|
|
|
35
46
|
|
|
36
47
|
See [Profiles](./profiles.md) for the full layout reference, inheritance, and prompt-include rules. A catalog can also publish a shared base profile marked `template: true` that role profiles inherit from without the base itself appearing as a launchable choice.
|
|
37
48
|
|
|
49
|
+
## Publishing skills
|
|
50
|
+
|
|
51
|
+
Publish a standalone skill under the catalog's `skills/<skill-id>/SKILL.md` or
|
|
52
|
+
`.outfitter/skills/<skill-id>/SKILL.md`. The folder name is the skill ID, and the
|
|
53
|
+
standard `name` in `SKILL.md` MUST match it. See [Skills](./skills.md) for the
|
|
54
|
+
complete definition and reference format.
|
|
55
|
+
|
|
56
|
+
Standalone catalog skills are independent of catalog profiles. A consumer can
|
|
57
|
+
select one from an existing local profile without inheriting any profile from
|
|
58
|
+
the publishing repository:
|
|
59
|
+
|
|
60
|
+
```yaml
|
|
61
|
+
# ~/.outfitter/settings.yml
|
|
62
|
+
default_profile: platform
|
|
63
|
+
default_agent: pi
|
|
64
|
+
profile_sources:
|
|
65
|
+
- github: ai-outfitter/actions
|
|
66
|
+
ref: v1
|
|
67
|
+
path: .outfitter
|
|
68
|
+
- path: ./profiles
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
```yaml
|
|
72
|
+
# ~/.outfitter/profiles/platform/profile.yml
|
|
73
|
+
id: platform
|
|
74
|
+
label: Platform
|
|
75
|
+
|
|
76
|
+
controls:
|
|
77
|
+
skills:
|
|
78
|
+
- outfitter-actions
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
The catalog root is the directory containing `profiles/` (and `settings.yml`,
|
|
82
|
+
when present). Outfitter discovers `skills/` beside `profiles/` at that root,
|
|
83
|
+
so a catalog may publish standalone skills without publishing placeholder
|
|
84
|
+
profiles. A source whose `path:` points directly at the profiles directory
|
|
85
|
+
keeps working; its parent is the catalog root.
|
|
86
|
+
|
|
87
|
+
Skill IDs follow the same [layer precedence](./concepts.md#layer-precedence) as
|
|
88
|
+
profiles: project-local, project, user, then cached remote sources in
|
|
89
|
+
configured order. Outfitter reports shadowed IDs so consumers can see which
|
|
90
|
+
source supplies the selected skill.
|
|
91
|
+
|
|
92
|
+
A published skill can reuse human-maintained catalog documentation without
|
|
93
|
+
copying it into the skill folder: declare the document as a `file` reference,
|
|
94
|
+
which resolves inside the catalog's checkout (including its synced cache),
|
|
95
|
+
while `repo_file` references resolve inside the consumer's active project. See
|
|
96
|
+
[External references](./skills.md#external-references) for the two-root model
|
|
97
|
+
and trust rules.
|
|
98
|
+
|
|
38
99
|
## Consuming a catalog as a profile source
|
|
39
100
|
|
|
40
101
|
Add the repository to `profile_sources` in your user (`~/.outfitter/settings.yml`) or project (`.outfitter/settings.yml`) settings:
|
|
@@ -61,7 +122,9 @@ Each source entry is one of:
|
|
|
61
122
|
Remote entries (`github`/`uri`) additionally accept:
|
|
62
123
|
|
|
63
124
|
- `ref:` — a tag, branch, or commit to pin. With a `ref`, `outfitter sync` fetches and checks out exactly that ref. Without one, sync fast-forwards the repository's default branch, so you always track the catalog's latest state.
|
|
64
|
-
- `path:` —
|
|
125
|
+
- `path:` — the catalog root inside the repository (the directory containing
|
|
126
|
+
`profiles/` and, optionally, `skills/`), or the profiles directory itself as
|
|
127
|
+
in existing configurations — its parent is then the catalog root.
|
|
65
128
|
- `only:` / `except:` — filter which profile ids from the source are exposed. `only` is an allowlist; `except` is a blocklist.
|
|
66
129
|
|
|
67
130
|
## Remote settings
|
|
@@ -94,18 +157,23 @@ Private GitHub catalogs are an enterprise feature. When sync detects a private G
|
|
|
94
157
|
|
|
95
158
|
## Trust and review
|
|
96
159
|
|
|
97
|
-
Adding a catalog source means trusting its authors with your agent runtime.
|
|
160
|
+
Adding a catalog source means trusting its authors with your agent runtime.
|
|
161
|
+
Profiles and selected skills from a catalog can:
|
|
98
162
|
|
|
99
163
|
- **Inject extensions** into your agent launch (`controls.extensions`). Extensions are code that runs inside the agent process with full access to your system — files, network, and shell.
|
|
100
164
|
- **Add arbitrary CLI arguments** (`controls.args`) to the launched agent, which can change permission modes or other agent behavior.
|
|
101
165
|
- **Set environment variables** (`controls.environment`) for the agent process.
|
|
102
166
|
- **Shape prompts, skills, subagents, and DeepWork jobs** — steering what the agent does with the access it already has.
|
|
167
|
+
- **Provide skill references** — catalog `file` references are trusted with the
|
|
168
|
+
selected skill; see the [trust boundary](./skills.md#trust-boundary).
|
|
103
169
|
|
|
104
170
|
Before adding a source, review it:
|
|
105
171
|
|
|
106
172
|
1. Read every profile's `controls` — especially `extensions`, `args`, and `environment` — and any extension code the repository ships.
|
|
107
|
-
2.
|
|
108
|
-
3.
|
|
109
|
-
4.
|
|
173
|
+
2. Read every selected skill and its catalog-owned `file` references.
|
|
174
|
+
3. Check `remote_settings` targets: a settings file can add further profile sources you did not review.
|
|
175
|
+
4. Confirm the repository's ownership and that its maintainers are who you expect.
|
|
176
|
+
5. Prefer `only:` filters so you expose just the profiles you reviewed, and list
|
|
177
|
+
skills explicitly by ID in `controls.skills`.
|
|
110
178
|
|
|
111
179
|
For organization catalogs, pin a `ref:` (a tag or commit) rather than tracking the default branch. A pinned ref makes updates an explicit, reviewable action — bump the ref after reviewing the diff — instead of silently pulling whatever the catalog publishes next. Unpinned sources are convenient for catalogs you maintain yourself, but they mean `outfitter sync` executes-by-configuration whatever landed upstream.
|