@ai-outfitter/outfitter 1.5.0 → 1.6.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/README.md +11 -2
- package/code/pi-extension/src/outfitter-extension.js +9 -1
- package/dist/agents/AgentLaunch.d.ts +2 -2
- package/dist/agents/AgentLaunch.js +4 -3
- package/dist/agents/AgentLaunch.js.map +1 -1
- package/dist/agents/ClaudeStatePersistence.d.ts +18 -0
- package/dist/agents/ClaudeStatePersistence.js +225 -0
- package/dist/agents/ClaudeStatePersistence.js.map +1 -0
- package/dist/cli/OutfitterCli.js +1 -1
- package/dist/cli/OutfitterCli.js.map +1 -1
- package/dist/cli/commands/RunAgentCommand.js +76 -15
- package/dist/cli/commands/RunAgentCommand.js.map +1 -1
- package/dist/cli/commands/SyncCommand.d.ts +7 -3
- package/dist/cli/commands/SyncCommand.js +110 -20
- package/dist/cli/commands/SyncCommand.js.map +1 -1
- package/dist/projection/CodexMcp.d.ts +11 -0
- package/dist/projection/CodexMcp.js +143 -0
- package/dist/projection/CodexMcp.js.map +1 -0
- package/dist/projection/Materialize.d.ts +3 -1
- package/dist/projection/Materialize.js +7 -3
- package/dist/projection/Materialize.js.map +1 -1
- package/dist/projection/ProjectHarness.js +66 -29
- package/dist/projection/ProjectHarness.js.map +1 -1
- package/dist/projection/Projection.d.ts +2 -0
- package/dist/resolver/Layer.d.ts +5 -2
- package/dist/resolver/Layer.js +47 -8
- package/dist/resolver/Layer.js.map +1 -1
- package/dist/resolver/ResolverContext.d.ts +1 -1
- package/dist/resolver/ResolverContext.js +1 -1
- package/dist/resolver/ResolverContext.js.map +1 -1
- package/dist/resolver/ResolverValidation.d.ts +12 -1
- package/dist/resolver/ResolverValidation.js +30 -20
- package/dist/resolver/ResolverValidation.js.map +1 -1
- package/dist/schemas/settings.schema.json +1 -1
- package/dist/schemas/system-extension-hook.schema.json +38 -0
- package/dist/settings/Settings.d.ts +1 -1
- package/dist/settings/Settings.js +1 -1
- package/dist/settings/Settings.js.map +1 -1
- package/dist/settings/SettingsLoader.js.map +1 -1
- package/dist/setup/DefaultCatalog.d.ts +18 -0
- package/dist/setup/DefaultCatalog.js +74 -13
- package/dist/setup/DefaultCatalog.js.map +1 -1
- package/dist/sources/SourceCache.d.ts +20 -0
- package/dist/sources/SourceCache.js +31 -1
- package/dist/sources/SourceCache.js.map +1 -1
- package/dist/sources/TransitiveSources.d.ts +43 -0
- package/dist/sources/TransitiveSources.js +177 -0
- package/dist/sources/TransitiveSources.js.map +1 -0
- package/dist/system/SystemExtensionHook.d.ts +34 -0
- package/dist/system/SystemExtensionHook.js +190 -0
- package/dist/system/SystemExtensionHook.js.map +1 -0
- package/dist/validation/SchemaValidator.d.ts +1 -1
- package/dist/validation/SchemaValidator.js +2 -0
- package/dist/validation/SchemaValidator.js.map +1 -1
- package/docs/architecture/state_writeback_strategy.md +15 -3
- package/docs/documentation/README.md +14 -1
- package/docs/documentation/catalogs.md +61 -2
- package/docs/documentation/cli.md +2 -1
- package/docs/documentation/concepts.md +2 -2
- package/docs/documentation/hooks.md +33 -0
- package/docs/documentation/in-cluster.md +2 -0
- package/docs/documentation/migration.md +1 -1
- package/docs/documentation/personas.md +1 -1
- package/docs/documentation/settings.md +1 -1
- package/docs/documentation/state.md +27 -0
- package/docs/documentation/support-matrix.md +34 -23
- package/docs/documentation/usecases/org-onboarding-sdlc-report.md +143 -0
- package/docs/documentation/usecases/persona-reviews.md +1 -1
- package/docs/philosophy.md +20 -0
- package/package.json +1 -1
- package/src/schemas/settings.schema.json +1 -1
- package/src/schemas/system-extension-hook.schema.json +38 -0
|
@@ -1,24 +1,37 @@
|
|
|
1
1
|
// Bootstraps the one canonical default catalog at the immutable revision shipped by Outfitter.
|
|
2
|
-
import {
|
|
2
|
+
import { statSync } from 'node:fs';
|
|
3
3
|
import { join } from 'node:path';
|
|
4
4
|
import { runGit, syncRemoteRepositoryAtomically, tryReadCleanHead } from '../sources/GitRepository.js';
|
|
5
|
-
import { createRemoteRepositoryCachePath, redactEmbeddedSourceCredentials } from '../sources/SourceCache.js';
|
|
5
|
+
import { createRemoteRepositoryCachePath, encodeRemoteSourceSelection, formatRemoteSourceDisplay, isImmutableRef, isVersionTagRef, redactEmbeddedSourceCredentials, } from '../sources/SourceCache.js';
|
|
6
|
+
import { readDeclaredRemoteSources } from '../sources/TransitiveSources.js';
|
|
6
7
|
export const defaultCatalogSource = {
|
|
7
8
|
github: 'ai-outfitter/default-profiles',
|
|
8
9
|
// TODO(release-automation): when default-profiles publishes a new Release Please tag, open a
|
|
9
10
|
// conventional dependency-bump commit in Outfitter so its next Release Please release ships it.
|
|
10
11
|
ref: 'v1.1.0',
|
|
11
12
|
};
|
|
12
|
-
|
|
13
|
-
|
|
13
|
+
// A recognized payload marker must be a real directory, not merely a path that exists: a repository
|
|
14
|
+
// committing a regular file named `agents` is not a catalog and must not validate as one.
|
|
15
|
+
const isDirectory = (path) => {
|
|
16
|
+
try {
|
|
17
|
+
return statSync(path).isDirectory();
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
const hasAgentsDirectory = (root) => isDirectory(join(root, 'agents'));
|
|
24
|
+
// Any directory the resolver treats as a `.agents` payload container, colocated or at the root.
|
|
25
|
+
const hasAnyAgentsPayload = (root) => ['agents', 'skills', 'knowledge', 'commands'].some((directory) => isDirectory(join(root, directory))) ||
|
|
26
|
+
isDirectory(join(root, '.agents'));
|
|
14
27
|
const assertImmutableRef = (ref) => {
|
|
15
|
-
if (!
|
|
28
|
+
if (!isImmutableRef(ref)) {
|
|
16
29
|
throw new Error(`Default catalog ref '${ref}' must be a full commit SHA or version tag.`);
|
|
17
30
|
}
|
|
18
31
|
};
|
|
19
|
-
const resolveExpectedCommit = (root, ref) =>
|
|
20
|
-
const isPinnedCatalogCheckout = (root, ref) => {
|
|
21
|
-
if (!
|
|
32
|
+
const resolveExpectedCommit = (root, ref) => isVersionTagRef(ref) ? runGit(['-C', root, 'rev-parse', `refs/tags/${ref}^{commit}`]) : ref;
|
|
33
|
+
const isPinnedCatalogCheckout = (root, ref, hasPayload) => {
|
|
34
|
+
if (!hasPayload(root))
|
|
22
35
|
return false;
|
|
23
36
|
try {
|
|
24
37
|
return tryReadCleanHead(root) === resolveExpectedCommit(root, ref);
|
|
@@ -30,13 +43,14 @@ const isPinnedCatalogCheckout = (root, ref) => {
|
|
|
30
43
|
/** Fetches one immutable catalog revision into the same cache path used by normal source resolution. */
|
|
31
44
|
export const bootstrapPinnedCatalog = (input) => {
|
|
32
45
|
assertImmutableRef(input.source.ref);
|
|
46
|
+
const hasPayload = input.requirePayload ?? hasAgentsDirectory;
|
|
33
47
|
const root = createRemoteRepositoryCachePath(input.homeDirectory, input.source, input.cacheDirectory);
|
|
34
|
-
if (isPinnedCatalogCheckout(root, input.source.ref))
|
|
48
|
+
if (isPinnedCatalogCheckout(root, input.source.ref, hasPayload))
|
|
35
49
|
return { root, source: input.source };
|
|
36
50
|
try {
|
|
37
|
-
syncRemoteRepositoryAtomically({
|
|
51
|
+
(input.syncRepository ?? syncRemoteRepositoryAtomically)({
|
|
38
52
|
cachePath: root,
|
|
39
|
-
fetchRef:
|
|
53
|
+
fetchRef: isVersionTagRef(input.source.ref)
|
|
40
54
|
? `refs/tags/${input.source.ref}:refs/tags/${input.source.ref}`
|
|
41
55
|
: input.source.ref,
|
|
42
56
|
source: input.source,
|
|
@@ -45,7 +59,7 @@ export const bootstrapPinnedCatalog = (input) => {
|
|
|
45
59
|
// accepting a dirty tree here would cache one that `tryReadCleanHead` rejects on every
|
|
46
60
|
// subsequent read, re-fetching forever.
|
|
47
61
|
validate: (temporaryRoot) => {
|
|
48
|
-
if (!isPinnedCatalogCheckout(temporaryRoot, input.source.ref)) {
|
|
62
|
+
if (!isPinnedCatalogCheckout(temporaryRoot, input.source.ref, hasPayload)) {
|
|
49
63
|
throw new Error(`Fetched default catalog did not resolve to ${input.source.ref}.`);
|
|
50
64
|
}
|
|
51
65
|
return 0;
|
|
@@ -60,5 +74,52 @@ export const bootstrapPinnedCatalog = (input) => {
|
|
|
60
74
|
});
|
|
61
75
|
}
|
|
62
76
|
};
|
|
63
|
-
|
|
77
|
+
/**
|
|
78
|
+
* Bootstraps a pinned catalog and the pinned `github:` closure it declares, so a fresh install can
|
|
79
|
+
* resolve a default profile whose skills live in a depended-on catalog (the founder profile's
|
|
80
|
+
* persona skills, shipped by community-profiles) without a manual `outfitter sync`. The root failing
|
|
81
|
+
* to fetch is fatal (existing behavior); a declared dependency failing to fetch is best-effort —
|
|
82
|
+
* resolution reports it as unsynchronized rather than blocking setup on it.
|
|
83
|
+
*/
|
|
84
|
+
export const bootstrapPinnedClosure = (input) => {
|
|
85
|
+
const rootResult = bootstrapPinnedCatalog(input);
|
|
86
|
+
const visited = new Set([encodeRemoteSourceSelection(input.source)]);
|
|
87
|
+
let frontier = [
|
|
88
|
+
{ root: rootResult.root, label: formatRemoteSourceDisplay(input.source) },
|
|
89
|
+
];
|
|
90
|
+
while (frontier.length > 0) {
|
|
91
|
+
const next = [];
|
|
92
|
+
for (const parent of frontier) {
|
|
93
|
+
// A `github:` closure is always path-less, so the payload root is the checkout root.
|
|
94
|
+
for (const declared of readDeclaredRemoteSources(parent.root, parent.root, parent.label).sources) {
|
|
95
|
+
const key = encodeRemoteSourceSelection(declared.source);
|
|
96
|
+
if (visited.has(key))
|
|
97
|
+
continue;
|
|
98
|
+
visited.add(key);
|
|
99
|
+
try {
|
|
100
|
+
next.push({
|
|
101
|
+
root: bootstrapPinnedCatalog({
|
|
102
|
+
homeDirectory: input.homeDirectory,
|
|
103
|
+
cacheDirectory: input.cacheDirectory,
|
|
104
|
+
source: declared.source,
|
|
105
|
+
syncRepository: input.syncRepository,
|
|
106
|
+
// A dependency may ship only skills; do not require it to contain agents/.
|
|
107
|
+
requirePayload: hasAnyAgentsPayload,
|
|
108
|
+
}).root,
|
|
109
|
+
label: formatRemoteSourceDisplay(declared.source),
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
catch {
|
|
113
|
+
// Best-effort: a dependency that cannot be fetched is surfaced by resolution as an
|
|
114
|
+
// unsynchronized source (with `outfitter sync` guidance), never a setup-blocking throw.
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
frontier = next;
|
|
119
|
+
}
|
|
120
|
+
return rootResult;
|
|
121
|
+
};
|
|
122
|
+
/* v8 ignore next 2 -- the production entry fetches the real default catalog from github.com; tests
|
|
123
|
+
drive bootstrapPinnedClosure directly and SetupCommand injects a fake bootstrap. */
|
|
124
|
+
export const bootstrapDefaultCatalog = (homeDirectory, cacheDirectory) => bootstrapPinnedClosure({ homeDirectory, cacheDirectory, source: defaultCatalogSource });
|
|
64
125
|
//# sourceMappingURL=DefaultCatalog.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DefaultCatalog.js","sourceRoot":"","sources":["../../src/setup/DefaultCatalog.ts"],"names":[],"mappings":"AAAA,+FAA+F;AAC/F,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"DefaultCatalog.js","sourceRoot":"","sources":["../../src/setup/DefaultCatalog.ts"],"names":[],"mappings":"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"}
|
|
@@ -15,6 +15,10 @@ export declare const isRemoteSource: <T extends {
|
|
|
15
15
|
readonly uri?: string;
|
|
16
16
|
readonly github?: string;
|
|
17
17
|
}>(source: T) => source is T & RemoteSourceReference;
|
|
18
|
+
/** True for a ref that names one immutable revision: a full commit SHA or a version tag. */
|
|
19
|
+
export declare const isImmutableRef: (ref: string) => boolean;
|
|
20
|
+
/** True for an immutable ref that must be resolved to a commit via `rev-parse` (a version tag). */
|
|
21
|
+
export declare const isVersionTagRef: (ref: string) => boolean;
|
|
18
22
|
export declare const normalizeGitUri: (uri: string) => string;
|
|
19
23
|
export declare const normalizeRemoteSourceUri: (source: RemoteSourceReference) => string;
|
|
20
24
|
export declare const formatRemoteSourceDisplay: (source: RemoteSourceReference) => string;
|
|
@@ -23,7 +27,23 @@ export declare const redactSourceUriCredentials: (uri: string) => string;
|
|
|
23
27
|
export declare const redactEmbeddedSourceCredentials: (value: string) => string;
|
|
24
28
|
export declare const encodeSourceUri: (uri: string) => string;
|
|
25
29
|
export declare const encodeRemoteSource: (source: RemoteSourceReference) => string;
|
|
30
|
+
/**
|
|
31
|
+
* A source's graph identity for dedup, visited sets, and layer ordering. It differs from
|
|
32
|
+
* {@link encodeRemoteSource} (the *checkout cache* key) in two deliberate ways:
|
|
33
|
+
*
|
|
34
|
+
* - It includes the canonicalized `path`, because two sources into different subpaths of one repo+ref
|
|
35
|
+
* are distinct layers — deduping them on the path-less cache key would silently drop one.
|
|
36
|
+
* - It keys on the *raw* normalized URI, credentials included, not the redacted cache key. Two entries
|
|
37
|
+
* for the same repo with different credentials are distinct configured sources; collapsing them
|
|
38
|
+
* (the cache key redacts credentials, so both share it) could drop a valid source for a broken one.
|
|
39
|
+
* This value is used only in ephemeral in-memory dedup/visited sets — never persisted or logged —
|
|
40
|
+
* so carrying credentials here is safe (the cache path and every user-facing display use the
|
|
41
|
+
* redacted forms).
|
|
42
|
+
*/
|
|
43
|
+
export declare const encodeRemoteSourceSelection: (source: RemoteSourceReference) => string;
|
|
26
44
|
/** Resolves the one cache root used by sync, settings, layer discovery, and setup bootstrap. */
|
|
27
45
|
export declare const resolveRemoteRepositoryCacheRoot: (homeDirectory: string, cacheDirectory?: string) => string;
|
|
28
46
|
export declare const createRemoteRepositoryCachePath: (homeDirectory: string, source: RemoteSourceReference, cacheDirectory?: string) => string;
|
|
47
|
+
/** The `.agents` payload root a source selects inside its materialized repository checkout. */
|
|
48
|
+
export declare const resolveSourcePayloadRoot: (repositoryPath: string, source: RemoteSourceReference) => string;
|
|
29
49
|
export declare const resolveRemoteRepositorySubpath: (repositoryPath: string, subpath?: string) => string;
|
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
// Encodes remote `.agents` source cache paths and normalizes remote source references.
|
|
2
|
-
import { isAbsolute, join, relative, resolve } from 'node:path';
|
|
2
|
+
import { isAbsolute, join, posix, relative, resolve } from 'node:path';
|
|
3
3
|
/** Distinguishes remote (`uri`/`github`) sources from local `path` sources. */
|
|
4
4
|
export const isRemoteSource = (source) => source.uri !== undefined || source.github !== undefined;
|
|
5
|
+
const fullCommitPattern = /^[a-f0-9]{40}$/u;
|
|
6
|
+
const versionTagPattern = /^v\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/u;
|
|
7
|
+
/** True for a ref that names one immutable revision: a full commit SHA or a version tag. */
|
|
8
|
+
export const isImmutableRef = (ref) => fullCommitPattern.test(ref) || versionTagPattern.test(ref);
|
|
9
|
+
/** True for an immutable ref that must be resolved to a commit via `rev-parse` (a version tag). */
|
|
10
|
+
export const isVersionTagRef = (ref) => versionTagPattern.test(ref);
|
|
5
11
|
export const normalizeGitUri = (uri) => (uri.startsWith('git+') ? uri.slice('git+'.length) : uri);
|
|
6
12
|
export const normalizeRemoteSourceUri = (source) => {
|
|
7
13
|
if (source.uri !== undefined) {
|
|
@@ -32,9 +38,33 @@ export const redactSourceUriCredentials = (uri) => {
|
|
|
32
38
|
export const redactEmbeddedSourceCredentials = (value) => value.replace(/((?:git\+)?[A-Za-z][A-Za-z0-9+.-]*:\/\/)[^/@\s]+@/gu, '$1REDACTED@');
|
|
33
39
|
export const encodeSourceUri = (uri) => Buffer.from(redactSourceUriCredentials(uri), 'utf8').toString('base64url');
|
|
34
40
|
export const encodeRemoteSource = (source) => encodeSourceUri(`${normalizeRemoteSourceUri(source)}#${source.ref ?? ''}`);
|
|
41
|
+
// The payload subpath a source selects, canonicalized so identical selections share one identity:
|
|
42
|
+
// an omitted path, `.`, `./`, and `foo/..` all normalize to the repository root ('').
|
|
43
|
+
const canonicalSourcePath = (path) => {
|
|
44
|
+
if (path === undefined)
|
|
45
|
+
return '';
|
|
46
|
+
const normalized = posix.normalize(path).replace(/\/+$/u, '');
|
|
47
|
+
return normalized === '.' ? '' : normalized;
|
|
48
|
+
};
|
|
49
|
+
/**
|
|
50
|
+
* A source's graph identity for dedup, visited sets, and layer ordering. It differs from
|
|
51
|
+
* {@link encodeRemoteSource} (the *checkout cache* key) in two deliberate ways:
|
|
52
|
+
*
|
|
53
|
+
* - It includes the canonicalized `path`, because two sources into different subpaths of one repo+ref
|
|
54
|
+
* are distinct layers — deduping them on the path-less cache key would silently drop one.
|
|
55
|
+
* - It keys on the *raw* normalized URI, credentials included, not the redacted cache key. Two entries
|
|
56
|
+
* for the same repo with different credentials are distinct configured sources; collapsing them
|
|
57
|
+
* (the cache key redacts credentials, so both share it) could drop a valid source for a broken one.
|
|
58
|
+
* This value is used only in ephemeral in-memory dedup/visited sets — never persisted or logged —
|
|
59
|
+
* so carrying credentials here is safe (the cache path and every user-facing display use the
|
|
60
|
+
* redacted forms).
|
|
61
|
+
*/
|
|
62
|
+
export const encodeRemoteSourceSelection = (source) => `${normalizeRemoteSourceUri(source)}#${source.ref ?? ''} ${canonicalSourcePath(source.path)}`;
|
|
35
63
|
/** Resolves the one cache root used by sync, settings, layer discovery, and setup bootstrap. */
|
|
36
64
|
export const resolveRemoteRepositoryCacheRoot = (homeDirectory, cacheDirectory) => cacheDirectory ?? join(homeDirectory, '.agents', 'cache');
|
|
37
65
|
export const createRemoteRepositoryCachePath = (homeDirectory, source, cacheDirectory) => join(resolveRemoteRepositoryCacheRoot(homeDirectory, cacheDirectory), 'repos', encodeRemoteSource(source));
|
|
66
|
+
/** The `.agents` payload root a source selects inside its materialized repository checkout. */
|
|
67
|
+
export const resolveSourcePayloadRoot = (repositoryPath, source) => source.path === undefined ? repositoryPath : resolveRemoteRepositorySubpath(repositoryPath, source.path);
|
|
38
68
|
export const resolveRemoteRepositorySubpath = (repositoryPath, subpath = '') => {
|
|
39
69
|
if (isAbsolute(subpath)) {
|
|
40
70
|
throw new Error(`Remote repository path '${subpath}' must be relative.`);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SourceCache.js","sourceRoot":"","sources":["../../src/sources/SourceCache.ts"],"names":[],"mappings":"AAAA,uFAAuF;AACvF,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"SourceCache.js","sourceRoot":"","sources":["../../src/sources/SourceCache.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { SourceReference } from '../settings/Settings.js';
|
|
2
|
+
import type { RemoteSourceReference } from './SourceCache.js';
|
|
3
|
+
/** A `github:` catalog dependency, pinned to an immutable ref, attributed to its declaring catalog. */
|
|
4
|
+
export interface DeclaredRemoteSource {
|
|
5
|
+
readonly source: {
|
|
6
|
+
readonly github: string;
|
|
7
|
+
readonly ref: string;
|
|
8
|
+
};
|
|
9
|
+
/** Display name of the declaring catalog, for warnings and provenance. */
|
|
10
|
+
readonly declaredBy: string;
|
|
11
|
+
}
|
|
12
|
+
export interface DeclaredSourcesResult {
|
|
13
|
+
readonly sources: readonly DeclaredRemoteSource[];
|
|
14
|
+
readonly warnings: readonly string[];
|
|
15
|
+
}
|
|
16
|
+
/** The settings file a catalog payload root may carry: `settings.yml`, else `.agents/settings.yml`. */
|
|
17
|
+
export declare const resolveCatalogSettingsPath: (payloadRoot: string) => string | undefined;
|
|
18
|
+
/**
|
|
19
|
+
* Reads the `github:` catalog dependencies a cached catalog declares (content, never policy —
|
|
20
|
+
* OFTR-004.6.2). Transitive sources are deliberately the narrowest safe subset: a `github:`
|
|
21
|
+
* shorthand pinned to an immutable ref. A `uri:` source, a local `path:` source, a `path:` subpath,
|
|
22
|
+
* or a mutable ref is skipped with a warning naming the declaring catalog (OFTR-004.6.4,
|
|
23
|
+
* OFTR-004.6.5). Restricting to `github:` means every transitive fetch during `outfitter sync`
|
|
24
|
+
* passes through the private-catalog gate and can never select an arbitrary git transport (a `uri:`
|
|
25
|
+
* could name `ext::<cmd>` or a filesystem path); forbidding `path:` means a declaration can never
|
|
26
|
+
* escape its fetched checkout. An unreadable or invalid settings file skips the catalog's
|
|
27
|
+
* declarations without failing resolution (OFTR-004.6.8). (First-party default-catalog bootstrap
|
|
28
|
+
* fetches its declared closure without that interactive gate — see OFTR-004.6.10.) Broader
|
|
29
|
+
* transports remain future work under the trust model in #212.
|
|
30
|
+
*/
|
|
31
|
+
export declare const readDeclaredRemoteSources: (payloadRoot: string, checkoutRoot: string, declaredBy: string) => DeclaredSourcesResult;
|
|
32
|
+
export interface TransitiveExpansionInput {
|
|
33
|
+
/** The directly configured sources, which seed the visited set and the first frontier. */
|
|
34
|
+
readonly directSources: readonly SourceReference[];
|
|
35
|
+
/** Maps a remote source to its cached checkout (clone) root, or undefined when it is not cached. */
|
|
36
|
+
readonly resolveCachedCheckoutRoot: (source: RemoteSourceReference) => string | undefined;
|
|
37
|
+
}
|
|
38
|
+
export interface TransitiveExpansionResult {
|
|
39
|
+
/** Newly discovered remote sources, breadth-first by depth then declaration order (OFTR-004.6.3). */
|
|
40
|
+
readonly sources: readonly DeclaredRemoteSource[];
|
|
41
|
+
readonly warnings: readonly string[];
|
|
42
|
+
}
|
|
43
|
+
export declare const expandTransitiveSources: (input: TransitiveExpansionInput) => TransitiveExpansionResult;
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
// Expands the remote-source closure that cached catalogs declare in their own settings files.
|
|
2
|
+
import { existsSync, lstatSync, realpathSync, statSync } from 'node:fs';
|
|
3
|
+
import { isAbsolute, join, relative, sep } from 'node:path';
|
|
4
|
+
import { createSettingsLoadPlan, loadSettingsFiles } from '../settings/SettingsLoader.js';
|
|
5
|
+
import { encodeRemoteSourceSelection, formatRemoteSourceDisplay, isImmutableRef, isRemoteSource, resolveSourcePayloadRoot, } from './SourceCache.js';
|
|
6
|
+
// True when a filesystem entry exists at the path, *including a dangling symlink*. `existsSync`
|
|
7
|
+
// follows links and reports a dangling one as absent, which would silently drop (rather than warn
|
|
8
|
+
// about, per OFTR-004.6.8) a `settings.yml` committed as a broken symlink.
|
|
9
|
+
const entryExists = (path) => {
|
|
10
|
+
try {
|
|
11
|
+
lstatSync(path);
|
|
12
|
+
return true;
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
/** The settings file a catalog payload root may carry: `settings.yml`, else `.agents/settings.yml`. */
|
|
19
|
+
export const resolveCatalogSettingsPath = (payloadRoot) => {
|
|
20
|
+
const rootSettingsPath = join(payloadRoot, 'settings.yml');
|
|
21
|
+
if (entryExists(rootSettingsPath))
|
|
22
|
+
return rootSettingsPath;
|
|
23
|
+
const nestedSettingsPath = join(payloadRoot, '.agents', 'settings.yml');
|
|
24
|
+
return entryExists(nestedSettingsPath) ? nestedSettingsPath : undefined;
|
|
25
|
+
};
|
|
26
|
+
/** Applies the safe-subset rules to one declared source, yielding a dependency or a skip reason. */
|
|
27
|
+
const classifyDeclaredSource = (declared, declaredBy) => {
|
|
28
|
+
if (!isRemoteSource(declared)) {
|
|
29
|
+
return {
|
|
30
|
+
skip: `Catalog '${declaredBy}' declares local path source '${declared.path}'; only 'github:' shorthands are resolved transitively, so it was skipped.`,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
if (declared.github === undefined) {
|
|
34
|
+
return {
|
|
35
|
+
skip: `Catalog '${declaredBy}' declares source '${formatRemoteSourceDisplay(declared)}' as a 'uri:'; only 'github:' shorthands are resolved transitively, so it was skipped.`,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
if (declared.path !== undefined) {
|
|
39
|
+
return {
|
|
40
|
+
skip: `Catalog '${declaredBy}' declares source '${formatRemoteSourceDisplay(declared)}' with a 'path:' subpath; transitive 'github:' sources must resolve their whole repository, so it was skipped.`,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
if (declared.ref === undefined || !isImmutableRef(declared.ref)) {
|
|
44
|
+
return {
|
|
45
|
+
skip: `Catalog '${declaredBy}' declares source '${formatRemoteSourceDisplay(declared)}' without an immutable ref (a full commit SHA or version tag); it was skipped.`,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
return { source: { github: declared.github, ref: declared.ref } };
|
|
49
|
+
};
|
|
50
|
+
/**
|
|
51
|
+
* Loads a cached catalog's own settings file, guarding against a hostile `settings.yml` committed as
|
|
52
|
+
* a symlink to a directory or a file outside the checkout (a `readFileSync` on a directory throws
|
|
53
|
+
* `EISDIR` and would crash resolution). Returns the parsed sources list, or a skip warning.
|
|
54
|
+
*/
|
|
55
|
+
const loadCatalogSourceList = (payloadRoot, checkoutRoot, declaredBy) => {
|
|
56
|
+
const settingsPath = resolveCatalogSettingsPath(payloadRoot);
|
|
57
|
+
if (settingsPath === undefined)
|
|
58
|
+
return { sources: [] };
|
|
59
|
+
try {
|
|
60
|
+
if (!statSync(settingsPath).isFile())
|
|
61
|
+
throw new Error('settings.yml is not a regular file');
|
|
62
|
+
// Anchor containment to the *checkout* root (the clone Outfitter created), not the payload root:
|
|
63
|
+
// a `path:` selecting a symlinked subdirectory would make the payload root itself resolve
|
|
64
|
+
// outside the clone, so anchoring there would accept an external settings.yml as "contained".
|
|
65
|
+
const relativeToCheckout = relative(realpathSync(checkoutRoot), realpathSync(settingsPath));
|
|
66
|
+
// Precise containment: a leading `..` segment (not a filename that merely starts with `..`, such
|
|
67
|
+
// as a directory named `..catalog`) or an absolute result means the settings escaped the checkout.
|
|
68
|
+
if (relativeToCheckout === '..' || relativeToCheckout.startsWith(`..${sep}`) || isAbsolute(relativeToCheckout)) {
|
|
69
|
+
throw new Error('settings.yml resolves outside the checkout');
|
|
70
|
+
}
|
|
71
|
+
// Known-accepted TOCTOU: the checkout could be swapped between this stat/realpath check and the
|
|
72
|
+
// read below. The checkout is a pinned immutable git ref that only Outfitter writes, and racing
|
|
73
|
+
// local-filesystem writes already imply broader access, so closing this window is not warranted.
|
|
74
|
+
const loaded = loadSettingsFiles(createSettingsLoadPlan([{ scope: 'remote', path: settingsPath }]));
|
|
75
|
+
if (loaded.issues.length > 0) {
|
|
76
|
+
return {
|
|
77
|
+
warning: `Catalog '${declaredBy}' declares invalid settings at '${settingsPath}'; its sources were skipped.`,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
return { sources: loaded.files[0].settings.sources ?? [] };
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
return {
|
|
84
|
+
warning: `Catalog '${declaredBy}' has an unreadable settings.yml (not a regular file inside the checkout); its sources were skipped.`,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
/**
|
|
89
|
+
* Reads the `github:` catalog dependencies a cached catalog declares (content, never policy —
|
|
90
|
+
* OFTR-004.6.2). Transitive sources are deliberately the narrowest safe subset: a `github:`
|
|
91
|
+
* shorthand pinned to an immutable ref. A `uri:` source, a local `path:` source, a `path:` subpath,
|
|
92
|
+
* or a mutable ref is skipped with a warning naming the declaring catalog (OFTR-004.6.4,
|
|
93
|
+
* OFTR-004.6.5). Restricting to `github:` means every transitive fetch during `outfitter sync`
|
|
94
|
+
* passes through the private-catalog gate and can never select an arbitrary git transport (a `uri:`
|
|
95
|
+
* could name `ext::<cmd>` or a filesystem path); forbidding `path:` means a declaration can never
|
|
96
|
+
* escape its fetched checkout. An unreadable or invalid settings file skips the catalog's
|
|
97
|
+
* declarations without failing resolution (OFTR-004.6.8). (First-party default-catalog bootstrap
|
|
98
|
+
* fetches its declared closure without that interactive gate — see OFTR-004.6.10.) Broader
|
|
99
|
+
* transports remain future work under the trust model in #212.
|
|
100
|
+
*/
|
|
101
|
+
export const readDeclaredRemoteSources = (payloadRoot, checkoutRoot, declaredBy) => {
|
|
102
|
+
const loaded = loadCatalogSourceList(payloadRoot, checkoutRoot, declaredBy);
|
|
103
|
+
if ('warning' in loaded)
|
|
104
|
+
return { sources: [], warnings: [loaded.warning] };
|
|
105
|
+
const sources = [];
|
|
106
|
+
const warnings = [];
|
|
107
|
+
for (const declared of loaded.sources) {
|
|
108
|
+
const classified = classifyDeclaredSource(declared, declaredBy);
|
|
109
|
+
if ('skip' in classified)
|
|
110
|
+
warnings.push(classified.skip);
|
|
111
|
+
else
|
|
112
|
+
sources.push({ source: classified.source, declaredBy });
|
|
113
|
+
}
|
|
114
|
+
return { sources, warnings };
|
|
115
|
+
};
|
|
116
|
+
/**
|
|
117
|
+
* Walks declared sources breadth-first from the directly configured sources over the local cache.
|
|
118
|
+
* A source already visited — directly configured or declared by an earlier catalog — is never
|
|
119
|
+
* expanded again, so dependency cycles terminate (OFTR-004.6.6). An uncached source stays in the
|
|
120
|
+
* result (the caller reports it) but its own declarations are unknowable until it is synced.
|
|
121
|
+
*/
|
|
122
|
+
// Reads one parent's cached checkout and returns the declarations it contributes. Returns `undefined`
|
|
123
|
+
// (skip this parent) when it is uncached or its `path:` is absolute/escaping — a throw here would
|
|
124
|
+
// otherwise crash resolution; only a directly configured source can carry such a path.
|
|
125
|
+
const declarationsFromParent = (parent, resolveCachedCheckoutRoot) => {
|
|
126
|
+
const checkoutRoot = resolveCachedCheckoutRoot(parent);
|
|
127
|
+
if (checkoutRoot === undefined)
|
|
128
|
+
return undefined;
|
|
129
|
+
let payloadRoot;
|
|
130
|
+
try {
|
|
131
|
+
payloadRoot = resolveSourcePayloadRoot(checkoutRoot, parent);
|
|
132
|
+
}
|
|
133
|
+
catch {
|
|
134
|
+
return undefined;
|
|
135
|
+
}
|
|
136
|
+
if (!existsSync(payloadRoot))
|
|
137
|
+
return undefined;
|
|
138
|
+
return readDeclaredRemoteSources(payloadRoot, checkoutRoot, formatRemoteSourceDisplay(parent));
|
|
139
|
+
};
|
|
140
|
+
export const expandTransitiveSources = (input) => {
|
|
141
|
+
const sources = [];
|
|
142
|
+
const warnings = [];
|
|
143
|
+
const visited = new Set();
|
|
144
|
+
// Seed the visited set with each direct source's path-aware identity so a directly configured
|
|
145
|
+
// subpath source does not suppress a transitive whole-repository source of the same repo+ref, and
|
|
146
|
+
// so an exact duplicate direct source is expanded only once (OFTR-004.6.6).
|
|
147
|
+
let frontier = [];
|
|
148
|
+
for (const direct of input.directSources) {
|
|
149
|
+
if (!isRemoteSource(direct))
|
|
150
|
+
continue;
|
|
151
|
+
const key = encodeRemoteSourceSelection(direct);
|
|
152
|
+
if (visited.has(key))
|
|
153
|
+
continue;
|
|
154
|
+
visited.add(key);
|
|
155
|
+
frontier.push(direct);
|
|
156
|
+
}
|
|
157
|
+
while (frontier.length > 0) {
|
|
158
|
+
const next = [];
|
|
159
|
+
for (const parent of frontier) {
|
|
160
|
+
const declared = declarationsFromParent(parent, input.resolveCachedCheckoutRoot);
|
|
161
|
+
if (declared === undefined)
|
|
162
|
+
continue;
|
|
163
|
+
warnings.push(...declared.warnings);
|
|
164
|
+
for (const entry of declared.sources) {
|
|
165
|
+
const key = encodeRemoteSourceSelection(entry.source);
|
|
166
|
+
if (visited.has(key))
|
|
167
|
+
continue;
|
|
168
|
+
visited.add(key);
|
|
169
|
+
sources.push(entry);
|
|
170
|
+
next.push(entry.source);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
frontier = next;
|
|
174
|
+
}
|
|
175
|
+
return { sources, warnings };
|
|
176
|
+
};
|
|
177
|
+
//# sourceMappingURL=TransitiveSources.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TransitiveSources.js","sourceRoot":"","sources":["../../src/sources/TransitiveSources.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { AgentLaunchPlan } from '../projection/Projection.js';
|
|
2
|
+
import type { Harness } from '../settings/Settings.js';
|
|
3
|
+
export interface SystemExtensionHarnessHook {
|
|
4
|
+
readonly extensions?: readonly string[];
|
|
5
|
+
readonly env?: Readonly<Record<string, string>>;
|
|
6
|
+
}
|
|
7
|
+
export interface SystemExtensionHook {
|
|
8
|
+
readonly filePath: string;
|
|
9
|
+
readonly name: string;
|
|
10
|
+
readonly harnesses: Partial<Readonly<Record<Harness, SystemExtensionHarnessHook>>>;
|
|
11
|
+
}
|
|
12
|
+
export interface SystemExtensionHookSource {
|
|
13
|
+
readonly directory: string;
|
|
14
|
+
readonly stamp: string;
|
|
15
|
+
}
|
|
16
|
+
export interface LoadedSystemExtensionHooks {
|
|
17
|
+
readonly hooks: readonly SystemExtensionHook[];
|
|
18
|
+
readonly source?: SystemExtensionHookSource;
|
|
19
|
+
}
|
|
20
|
+
export interface AttachedSystemExtensionHooks {
|
|
21
|
+
readonly launch: AgentLaunchPlan;
|
|
22
|
+
readonly warnings: readonly string[];
|
|
23
|
+
}
|
|
24
|
+
export interface SystemExtensionHookDiscoveryInput {
|
|
25
|
+
readonly environment?: NodeJS.ProcessEnv;
|
|
26
|
+
readonly platform?: NodeJS.Platform;
|
|
27
|
+
}
|
|
28
|
+
export declare const resolveSystemExtensionHookSource: (input?: SystemExtensionHookDiscoveryInput) => SystemExtensionHookSource | undefined;
|
|
29
|
+
export declare const readSystemExtensionHooks: (input?: SystemExtensionHookDiscoveryInput) => LoadedSystemExtensionHooks;
|
|
30
|
+
/**
|
|
31
|
+
* Prepends every system Pi extension after projection, including for RPC/print launches. Hook env
|
|
32
|
+
* stays beneath the projected plan env so it cannot replace Outfitter's runtime/session paths.
|
|
33
|
+
*/
|
|
34
|
+
export declare const attachSystemExtensionHooks: (plan: AgentLaunchPlan, loaded?: LoadedSystemExtensionHooks) => AttachedSystemExtensionHooks;
|