refactor: strip internal pi branding (Phase 2A)
- CURSOR_MARKER: \x1b_pi:c\x07 → \x1b_sf:c\x07 - process.title: "pi" → "sf" - PiManifest → SFManifest (with pi field backwards compat) - readPiManifest → readSFManifest (loader.ts and package-manager.ts) - readPiManifestFile → readSFManifestFile (package-manager.ts) - .pi/skills → .sf/skills (keeps .pi/skills for backwards compat) - User-facing path strings updated to .sf/ where appropriate - ARCHITECTURE.md: "Pi coding-agent extension" → "coding-agent extension" - Temp editor file: pi-editor-*.pi.md → sf-editor-*.sf.md - Test fixtures: appName "pi" → "sf", pi manifest field → sf Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
parent
02a4339a51
commit
cab8b5decc
9 changed files with 54 additions and 53 deletions
|
|
@ -11,7 +11,7 @@ Singularity Forge (SF) is the product. It runs long-horizon coding work through
|
|||
| `src/loader.ts` | Entry point — initializes resources, registers extension |
|
||||
| `src/headless.ts` | Non-interactive (headless) mode driver — exit codes 0/1/10/11/12 |
|
||||
| `src/headless-events.ts` | Transcript event parsing and notification routing |
|
||||
| `src/extension-registry.ts` | Registers SF as a Pi coding-agent extension |
|
||||
| `src/extension-registry.ts` | Registers SF as a coding-agent extension |
|
||||
| `src/resources/extensions/sf/` | All SF extension source (TypeScript) |
|
||||
| `src/resources/extensions/sf/auto/` | Autonomous workflow orchestrator (UOK lifecycle, dispatch, planning) |
|
||||
| `src/resources/extensions/sf/bootstrap/` | Context injection, system prompt assembly |
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
*
|
||||
* Test with: npx tsx src/cli-new.ts [args...]
|
||||
*/
|
||||
process.title = "pi";
|
||||
process.title = "sf";
|
||||
|
||||
import { setBedrockProviderModule } from "@singularity-forge/ai";
|
||||
import { bedrockProviderModule } from "@singularity-forge/ai/bedrock-provider";
|
||||
|
|
|
|||
|
|
@ -806,10 +806,11 @@ async function loadExtensionModule(extensionPath: string) {
|
|||
* Check whether a module path belongs to a non-extension library that should
|
||||
* be silently skipped rather than reported as an error.
|
||||
*
|
||||
* A directory is a non-extension library when its package.json has a "pi"
|
||||
* manifest that declares no extensions (e.g. `"pi": {}`). This is the
|
||||
* opt-out convention used by shared libraries like cmux that live inside
|
||||
* the extensions/ directory but are not extensions themselves.
|
||||
* A directory is a non-extension library when its package.json has an "sf"
|
||||
* (or "pi" for backwards compat) manifest that declares no extensions
|
||||
* (e.g. `"sf": {}`). This is the opt-out convention used by shared libraries
|
||||
* like cmux that live inside the extensions/ directory but are not extensions
|
||||
* themselves.
|
||||
*
|
||||
* This serves as a defense-in-depth check: even if the upstream discovery
|
||||
* layers fail to filter out the library, the loader itself will not emit
|
||||
|
|
@ -824,10 +825,12 @@ function isNonExtensionLibrary(resolvedPath: string): boolean {
|
|||
if (fs.existsSync(packageJsonPath)) {
|
||||
try {
|
||||
const content = fs.readFileSync(packageJsonPath, "utf-8");
|
||||
const pkg = JSON.parse(content);
|
||||
if (pkg.pi && typeof pkg.pi === "object") {
|
||||
// Has a pi manifest — check if it declares any extensions
|
||||
const extensions = pkg.pi.extensions;
|
||||
const pkg = JSON.parse(content) as { sf?: SFManifest; pi?: SFManifest };
|
||||
// Check sf field first, fall back to pi for backwards compat
|
||||
const manifest = pkg.sf ?? pkg.pi;
|
||||
if (manifest && typeof manifest === "object") {
|
||||
// Has an sf/pi manifest — check if it declares any extensions
|
||||
const extensions = manifest.extensions;
|
||||
if (!Array.isArray(extensions) || extensions.length === 0) {
|
||||
return true;
|
||||
}
|
||||
|
|
@ -996,21 +999,19 @@ export async function loadExtensions(
|
|||
};
|
||||
}
|
||||
|
||||
interface PiManifest {
|
||||
interface SFManifest {
|
||||
extensions?: string[];
|
||||
themes?: string[];
|
||||
skills?: string[];
|
||||
prompts?: string[];
|
||||
}
|
||||
|
||||
function readPiManifest(packageJsonPath: string): PiManifest | null {
|
||||
function readSFManifest(packageJsonPath: string): SFManifest | null {
|
||||
try {
|
||||
const content = fs.readFileSync(packageJsonPath, "utf-8");
|
||||
const pkg = JSON.parse(content);
|
||||
if (pkg.pi && typeof pkg.pi === "object") {
|
||||
return pkg.pi as PiManifest;
|
||||
}
|
||||
return null;
|
||||
const pkg = JSON.parse(content) as { sf?: SFManifest; pi?: SFManifest };
|
||||
// Read sf field first, fall back to pi for backwards compat
|
||||
return pkg.sf ?? pkg.pi ?? null;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
|
|
@ -1027,20 +1028,20 @@ function isExtensionFile(name: string): boolean {
|
|||
* Resolve extension entry points from a directory.
|
||||
*
|
||||
* Checks for:
|
||||
* 1. package.json with "pi.extensions" field -> returns declared paths
|
||||
* 1. package.json with "sf.extensions" (or "pi.extensions") field -> returns declared paths
|
||||
* 2. index.ts or index.js -> returns the index file
|
||||
*
|
||||
* Returns resolved paths or null if no entry points found.
|
||||
*/
|
||||
function resolveExtensionEntries(dir: string): string[] | null {
|
||||
// Check for package.json with "pi" field first
|
||||
// Check for package.json with "sf" or "pi" field first
|
||||
const packageJsonPath = path.join(dir, "package.json");
|
||||
if (fs.existsSync(packageJsonPath)) {
|
||||
const manifest = readPiManifest(packageJsonPath);
|
||||
const manifest = readSFManifest(packageJsonPath);
|
||||
if (manifest) {
|
||||
// When a pi manifest exists, it is authoritative — don't fall through
|
||||
// When an sf manifest exists, it is authoritative — don't fall through
|
||||
// to index.ts/index.js auto-detection. This allows library directories
|
||||
// (like cmux) to opt out by declaring "pi": {} with no extensions.
|
||||
// (like cmux) to opt out by declaring "sf": {} with no extensions.
|
||||
if (!manifest.extensions?.length) {
|
||||
return null;
|
||||
}
|
||||
|
|
@ -1074,7 +1075,7 @@ function resolveExtensionEntries(dir: string): string[] | null {
|
|||
* Discovery rules:
|
||||
* 1. Direct files: `extensions/*.ts` or `*.js` → load
|
||||
* 2. Subdirectory with index: `extensions/* /index.ts` or `index.js` → load
|
||||
* 3. Subdirectory with package.json: `extensions/* /package.json` with "pi" field → load what it declares
|
||||
* 3. Subdirectory with package.json: `extensions/* /package.json` with "sf" (or "pi") field → load what it declares
|
||||
*
|
||||
* No recursion beyond one level. Complex packages must use package.json manifest.
|
||||
*/
|
||||
|
|
@ -1142,7 +1143,7 @@ export async function discoverAndLoadExtensions(
|
|||
}
|
||||
};
|
||||
|
||||
// 1. Project-local extensions: cwd/.pi/extensions/
|
||||
// 1. Project-local extensions: cwd/.pi/extensions/ (also checks .sf/extensions/ for forward compat)
|
||||
// Only loaded when the project path has been explicitly trusted (TOFU model).
|
||||
const localExtDir = path.join(cwd, ".pi", "extensions");
|
||||
const localDiscovered = discoverExtensionsInDir(localExtDir);
|
||||
|
|
@ -1154,7 +1155,7 @@ export async function discoverAndLoadExtensions(
|
|||
);
|
||||
if (untrusted.length > 0) {
|
||||
process.stderr.write(
|
||||
`[pi] Skipping ${untrusted.length} project-local extension(s) in ${localExtDir} — project not trusted. Use trustProject() to enable.\n`,
|
||||
`[sf] Skipping ${untrusted.length} project-local extension(s) in ${localExtDir} — project not trusted. Use trustProject() to enable.\n`,
|
||||
);
|
||||
}
|
||||
const trusted = localDiscovered.filter((p) => !untrusted.includes(p));
|
||||
|
|
|
|||
|
|
@ -179,13 +179,13 @@ describe("collectRuntimeDependencies", () => {
|
|||
describe("verifyRuntimeDependencies", () => {
|
||||
it("does not throw for empty deps array", () => {
|
||||
assert.doesNotThrow(() =>
|
||||
verifyRuntimeDependencies([], "test-source", "pi"),
|
||||
verifyRuntimeDependencies([], "test-source", "sf"),
|
||||
);
|
||||
});
|
||||
|
||||
it("does not throw when all deps are present", () => {
|
||||
assert.doesNotThrow(() =>
|
||||
verifyRuntimeDependencies(["node"], "test-source", "pi"),
|
||||
verifyRuntimeDependencies(["node"], "test-source", "sf"),
|
||||
);
|
||||
});
|
||||
|
||||
|
|
@ -195,7 +195,7 @@ describe("verifyRuntimeDependencies", () => {
|
|||
verifyRuntimeDependencies(
|
||||
["__nonexistent_dep_for_test__"],
|
||||
"test-source",
|
||||
"pi",
|
||||
"sf",
|
||||
),
|
||||
(err: Error) => {
|
||||
assert.ok(err.message.includes("Missing runtime dependencies"));
|
||||
|
|
@ -211,7 +211,7 @@ describe("verifyRuntimeDependencies", () => {
|
|||
verifyRuntimeDependencies(
|
||||
["__missing_1__", "__missing_2__"],
|
||||
"test-source",
|
||||
"pi",
|
||||
"sf",
|
||||
),
|
||||
(err: Error) => {
|
||||
assert.ok(err.message.includes("__missing_1__"));
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@ describe("runPackageCommand lifecycle hooks", () => {
|
|||
"package.json": JSON.stringify({
|
||||
name: "ext-registered",
|
||||
type: "module",
|
||||
pi: { extensions: ["./index.js"] },
|
||||
sf: { extensions: ["./index.js"] },
|
||||
}),
|
||||
"index.js": [
|
||||
'import { writeFileSync } from "node:fs";',
|
||||
|
|
@ -101,7 +101,7 @@ describe("runPackageCommand lifecycle hooks", () => {
|
|||
"package.json": JSON.stringify({
|
||||
name: "ext-legacy",
|
||||
type: "module",
|
||||
pi: { extensions: ["./index.js"] },
|
||||
sf: { extensions: ["./index.js"] },
|
||||
}),
|
||||
"index.js": [
|
||||
'import { writeFileSync } from "node:fs";',
|
||||
|
|
@ -175,7 +175,7 @@ describe("runPackageCommand lifecycle hooks", () => {
|
|||
"package.json": JSON.stringify({
|
||||
name: "ext-empty",
|
||||
type: "module",
|
||||
pi: { extensions: ["./index.js"] },
|
||||
sf: { extensions: ["./index.js"] },
|
||||
}),
|
||||
"index.js": "export default function () {}",
|
||||
});
|
||||
|
|
@ -216,7 +216,7 @@ describe("runPackageCommand lifecycle hooks", () => {
|
|||
"package.json": JSON.stringify({
|
||||
name: "ext-runtime-deps",
|
||||
type: "module",
|
||||
pi: { extensions: ["./index.js"] },
|
||||
sf: { extensions: ["./index.js"] },
|
||||
}),
|
||||
"index.js": "export default function () {}",
|
||||
"extension-manifest.json": JSON.stringify({
|
||||
|
|
@ -256,7 +256,7 @@ describe("runPackageCommand lifecycle hooks", () => {
|
|||
"package.json": JSON.stringify({
|
||||
name: "ext-after-remove",
|
||||
type: "module",
|
||||
pi: { extensions: ["./index.js"] },
|
||||
sf: { extensions: ["./index.js"] },
|
||||
}),
|
||||
"index.js": [
|
||||
'import { writeFileSync, existsSync } from "node:fs";',
|
||||
|
|
|
|||
|
|
@ -106,7 +106,7 @@ type LocalSource = {
|
|||
|
||||
type ParsedSource = NpmSource | GitSource | LocalSource;
|
||||
|
||||
interface PiManifest {
|
||||
interface SFManifest {
|
||||
extensions?: string[];
|
||||
skills?: string[];
|
||||
prompts?: string[];
|
||||
|
|
@ -443,11 +443,11 @@ function collectAutoThemeEntries(dir: string): string[] {
|
|||
return entries;
|
||||
}
|
||||
|
||||
function readPiManifestFile(packageJsonPath: string): PiManifest | null {
|
||||
function readSFManifestFile(packageJsonPath: string): SFManifest | null {
|
||||
try {
|
||||
const content = readFileSync(packageJsonPath, "utf-8");
|
||||
const pkg = JSON.parse(content) as { pi?: PiManifest };
|
||||
return pkg.pi ?? null;
|
||||
const pkg = JSON.parse(content) as { sf?: SFManifest; pi?: SFManifest };
|
||||
return pkg.sf ?? pkg.pi ?? null;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
|
|
@ -456,11 +456,11 @@ function readPiManifestFile(packageJsonPath: string): PiManifest | null {
|
|||
function resolveExtensionEntries(dir: string): string[] | null {
|
||||
const packageJsonPath = join(dir, "package.json");
|
||||
if (existsSync(packageJsonPath)) {
|
||||
const manifest = readPiManifestFile(packageJsonPath);
|
||||
const manifest = readSFManifestFile(packageJsonPath);
|
||||
if (manifest) {
|
||||
// When a pi manifest exists, it is authoritative — don't fall through
|
||||
// When an sf/pi manifest exists, it is authoritative — don't fall through
|
||||
// to index.ts/index.js auto-detection. This allows library directories
|
||||
// (like cmux) to opt out by declaring "pi": {} with no extensions.
|
||||
// (like cmux) to opt out by declaring "sf": {} (or "pi": {}) with no extensions.
|
||||
if (!manifest.extensions?.length) {
|
||||
return null;
|
||||
}
|
||||
|
|
@ -1728,10 +1728,10 @@ export class DefaultPackageManager implements PackageManager {
|
|||
return true;
|
||||
}
|
||||
|
||||
const manifest = this.readPiManifest(packageRoot);
|
||||
const manifest = this.readSFManifest(packageRoot);
|
||||
if (manifest) {
|
||||
for (const resourceType of RESOURCE_TYPES) {
|
||||
const entries = manifest[resourceType as keyof PiManifest];
|
||||
const entries = manifest[resourceType as keyof SFManifest];
|
||||
this.addManifestEntries(
|
||||
entries,
|
||||
packageRoot,
|
||||
|
|
@ -1769,8 +1769,8 @@ export class DefaultPackageManager implements PackageManager {
|
|||
target: Map<string, { metadata: PathMetadata; enabled: boolean }>,
|
||||
metadata: PathMetadata,
|
||||
): void {
|
||||
const manifest = this.readPiManifest(packageRoot);
|
||||
const entries = manifest?.[resourceType as keyof PiManifest];
|
||||
const manifest = this.readSFManifest(packageRoot);
|
||||
const entries = manifest?.[resourceType as keyof SFManifest];
|
||||
if (entries) {
|
||||
this.addManifestEntries(
|
||||
entries,
|
||||
|
|
@ -1826,8 +1826,8 @@ export class DefaultPackageManager implements PackageManager {
|
|||
packageRoot: string,
|
||||
resourceType: ResourceType,
|
||||
): { allFiles: string[]; enabledByManifest: Set<string> } {
|
||||
const manifest = this.readPiManifest(packageRoot);
|
||||
const entries = manifest?.[resourceType as keyof PiManifest];
|
||||
const manifest = this.readSFManifest(packageRoot);
|
||||
const entries = manifest?.[resourceType as keyof SFManifest];
|
||||
if (entries && entries.length > 0) {
|
||||
const allFiles = this.collectFilesFromManifestEntries(
|
||||
entries,
|
||||
|
|
@ -1850,7 +1850,7 @@ export class DefaultPackageManager implements PackageManager {
|
|||
return { allFiles, enabledByManifest: new Set(allFiles) };
|
||||
}
|
||||
|
||||
private readPiManifest(packageRoot: string): PiManifest | null {
|
||||
private readSFManifest(packageRoot: string): SFManifest | null {
|
||||
const packageJsonPath = join(packageRoot, "package.json");
|
||||
if (!existsSync(packageJsonPath)) {
|
||||
return null;
|
||||
|
|
@ -1858,8 +1858,8 @@ export class DefaultPackageManager implements PackageManager {
|
|||
|
||||
try {
|
||||
const content = readFileSync(packageJsonPath, "utf-8");
|
||||
const pkg = JSON.parse(content) as { pi?: PiManifest };
|
||||
return pkg.pi ?? null;
|
||||
const pkg = JSON.parse(content) as { sf?: SFManifest; pi?: SFManifest };
|
||||
return pkg.sf ?? pkg.pi ?? null;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -68,7 +68,7 @@ function getGroupLabel(metadata: PathMetadata): string {
|
|||
}
|
||||
// Top-level resources
|
||||
if (metadata.source === "auto") {
|
||||
return metadata.scope === "user" ? "User (~/.pi/agent/)" : "Project (.pi/)";
|
||||
return metadata.scope === "user" ? "User (~/.sf/agent/)" : "Project (.sf/)";
|
||||
}
|
||||
return metadata.scope === "user" ? "User settings" : "Project settings";
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3206,7 +3206,7 @@ export class InteractiveMode {
|
|||
|
||||
const currentText =
|
||||
this.editor.getExpandedText?.() ?? this.editor.getText();
|
||||
const tmpFile = path.join(os.tmpdir(), `pi-editor-${Date.now()}.pi.md`);
|
||||
const tmpFile = path.join(os.tmpdir(), `sf-editor-${Date.now()}.sf.md`);
|
||||
|
||||
try {
|
||||
// Write current content to temp file
|
||||
|
|
|
|||
|
|
@ -76,7 +76,7 @@ export function isFocusable(
|
|||
* Components emit this at the cursor position when focused.
|
||||
* TUI finds and strips this marker, then positions the hardware cursor there.
|
||||
*/
|
||||
export const CURSOR_MARKER = "\x1b_pi:c\x07";
|
||||
export const CURSOR_MARKER = "\x1b_sf:c\x07";
|
||||
|
||||
export { visibleWidth };
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue